Toodledo iCal v2

Friday, 23 October 2009 06:04 PM
by Coose

I’m going to make this short, because I just typed up a full post and the piece of shit Windows Live Writer crashed…again…and I lost everything and don’t feel like typing it all again.

In a previous post, I described an iCal feed that I created to enhance the functionality of Toodledo’s iCal feed.  To restate the problem: The Toodledo iCal feed creates an all-day event for each task that has a due date.  The problem is that if a task overdue and not completed, it should show up on the current day, not the original due date.  Windows Mobile applications like Agenda One and Pocket Informant use this functionality, and I really got used to it.  Toodledo tasks are always displayed on the calendar feed on their original due date.

So, what I need to do is if the task is incomplete and overdue, modify the due date to today.  At first glance I thought of using the Toodledo API to create the calendar feed manually.  The problem is I have three young kids at home, and a million things on my plate.  So I need a quick and dirty solution.

What I originally opted to do is create a handler on my own web site to serve up the ics feed.  The handler would call the Toodledo ics feed, and modify any overdue start and end date in the resulting stream and set it to the current date.  Quick, easy, and effective.  Perfect.  Examining the ics feed manually, and testing in Sunbird and Outlook showed everything A-OK.

But when adding the feed to Google, it originally shows up correctly, but overdue tasks do not show up on the current day, like I coded.  Google does not allow a refresh of the subscribed calendars, so there’s no way to test immediately.  But after watching a few days, it seems that the calendar appears to cached on Google servers.  I think what I need to do is modify the start and end date of expired incomplete tasks, as well as the last modified date.  This should cue Google to update the calendar item.

So, the first thing to do is see what the original feed looks like.  Using one of the most useful tools in my toolbox, LINQPad, I we can see the resulting stream.  The address of the ics from Toodledo can be found by logging in to Toodledo, and navigating to http://www.toodledo.com/connect_ical.php.

The LINQPad statement to show the result is:
Encoding.ASCII.GetString(new WebClient().DownloadData("http://www.toodledo.com/id/tdXXXXXXXXXXXXX/ical_live.ics")).Dump();
Again, your URL will differ.  Make sure to change the “webcal://” protocol to “http://” as WebClient doesn’t understand what “webcal://” is.

The resulting data is:
BEGIN:VCALENDAR
PRODID:-//Toodledo iCal//www.toodledo.com//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Toodledo iCal
LAST-MODIFIED:20091023T220858Z
BEGIN:VEVENT
URL:http://www.toodledo.com
CREATED:20090822T000000
DTSTAMP:20090822T000000
LAST-MODIFIED:20090927T205903
UID:some-guid#toodledo.com
SUMMARY:Task Name
DESCRIPTION:
PRIORITY:5
STATUS:CONFIRMED
TRANSP:OPAQUE
DTSTART;VALUE=DATE:20090927
DTEND;VALUE=DATE:20090927

END:VEVENT
END:VCALENDAR

Toodledo only returns incomplete tasks in its feed, so we don’t have to worry about filtering on the status.  The lines in red are the lines that need to be modified for our customized feed.

So, create a handler for your feed name derived from IHttpHandler.  Override the ProcessRequest method to parse each VEVENT and if needed, adjust the dates.

    1 public void ProcessRequest(HttpContext context)

    2 {

    3     WebClient client = new WebClient();

    4     string toodledo = Encoding.ASCII.GetString(client.DownloadData("http://www.toodledo.com/id/tdXXXXXXXXXXXXX/ical_live.ics"));

    5 

    6     List<Dictionary<string, string>> items = new List<Dictionary<string, string>>();

    7 

    8     foreach (Match match in Regex.Matches(toodledo, @"BEGIN:VEVENT.*?END:VEVENT", RegexOptions.Singleline))

    9     {

   10         Dictionary<string, string> values = new Dictionary<string, string>();

   11 

   12         // now we have a bunch of name/value pairs

   13         foreach (string line in match.Value.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))

   14         {

   15             int x = line.IndexOf(':');

   16             if (x < 0)

   17             {

   18                 values[line] = string.Empty;

   19             }

   20             else

   21             {

   22                 values[line.Substring(0, x)] = (x >= line.Length - 1) ? string.Empty : line.Substring(x + 1);

   23             }

   24         }

   25 

   26         items.Add(values);

   27     }

   28 

   29     // adjust dates.

   30     // when adjusting the date, make sure to adjust modified date, etc.  Google seems to need this.

   31     foreach (Dictionary<string, string> item in items)

   32     {

   33         string[] keys = new string[] { "DTSTART;VALUE=DATE", "DTEND;VALUE=DATE" };

   34         foreach (string key in keys)

   35         {

   36             if (item.ContainsKey(key))

   37             {

   38                 DateTime currentDate = DateTime.ParseExact(item[key], "yyyyMMdd", CultureInfo.InvariantCulture);

   39                 if (currentDate < DateTime.Now)

   40                 {

   41                     item[key] = DateTime.Now.ToString("yyyyMMdd");

   42                     item["LAST-MODIFIED"] = DateTime.Now.ToString("yyyyMMddThhmmss");

   43                     item["DTSTAMP"] = DateTime.Now.ToString("yyyyMMddThhmmss");

   44                 }

   45             }

   46         }

   47     }

   48 

   49     StringBuilder sb = new StringBuilder();

   50 

   51     sb.AppendLine("BEGIN:VCALENDAR");

   52     sb.AppendLine("PRODID:-//Toodledo iCal//www.toodledo.com//EN");

   53     sb.AppendLine("VERSION:2.0");

   54     sb.AppendLine("CALSCALE:GREGORIAN");

   55     sb.AppendLine("METHOD:PUBLISH");

   56     sb.AppendLine("X-WR-CALNAME:Toodledo iCal");

   57     sb.Append("LAST-MODIFIED:");

   58     sb.Append(DateTime.UtcNow.ToString("yyyyMMddThhmmssZ"));

   59     sb.AppendLine();

   60 

   61     foreach (Dictionary<string, string> item in items)

   62     {

   63         foreach (KeyValuePair<string, string> pair in item)

   64         {

   65             sb.AppendFormat("{0}:{1}", pair.Key, pair.Value);

   66             sb.AppendLine();

   67         }

   68     }

   69 

   70     sb.AppendLine("END:VCALENDAR");

   71 

   72     context.Response.Cache.SetCacheability(HttpCacheability.NoCache);

   73     context.Response.Headers["Keep-Alive"] = "timeout=2, max=100";

   74     context.Response.Headers["Connection"] = "Keep-Alive";

   75     context.Response.ContentType = client.ResponseHeaders[HttpResponseHeader.ContentType];

   76     context.Response.Output.Write(sb.ToString());

   77 }

A quick overview of what is happening:

  • Line 4 downloads the full original feed from Toodledo.
  • Lines 6 – 27 find all text between “BEGIN:VEVENT” and “END:VEVENT”, and for each line adds a dictionary entry with the text to the left of the : as the key, and to the right as the value.
  • Lines 31 – 47 find any DTSTART;VALUE=DATE or DTEND;VALUE=DATE entries, parses the value into a date, and changes the date to today if the date is in the past, and also sets LAST-MODIFIED and DTSTAMP to today.
  • Lines 51 – 59 write the standard iCal information.
  • Line 58 changes the modified date of the entire iCal feed to today.
  • Lines 61 – 68 write each name/value pair of the dictionary back to the iCal.
  • Line 70 closes the iCal.
  • Lines 72 – 76 writes the iCal to the response stream.

Publish this handler on your web site, and add the URL of YOUR HANDLER to Google calendar, and your Toodledo tasks are “fixed”.

Comment on this
Mike's Blog
|

iPhone Ringtones from MP3

Sunday, 13 September 2009 07:46 PM
by coose

I just got this iPhone, and what a hassle trying to figure out how to create my own ringtones.  I think I have it down now.  I’ll create a ringtone from an MP3 file using only iTunes.  I’ll be demoing on iTunes 9.0.0.70.  iTunes is needed to copy the ringtone to the phone, but we will also use to to create an AAC encoded audio file.

[more Click here to see my process]

I have no music in my iTunes (I don’t like iTunes, and don’t use it to manage my music library or my iPods).  This makes it easier to follow this tutorial.

Import your MP3 into the iTunes library.  From the “File” menu, select “Add File to Library…”, or press Ctrl+O.

Add File to Library

Browse to the source MP3 file.  This will add the file to the iTunes library.

MP3 in Library

Right-click the file and select “Get Info”

Get Info

In the resulting dialog, check the “Start Time” and “Stop Time” checkboxes.  Enter in the start and stop times in the MP3 that you want to use for your ring tone.  Ringtones are limited to 30 seconds (some sources say that you can do 40 seconds…I haven’t tried).

Mp3Ringtone4

With the item selected in the library, select the “Advanced” menu, and select “Create AAC Version”.  This will create a copy of the MP3 file, only using the region that was selected in the previous step.

In cases like mine, where my music library is not controlled by iTunes, the source MP3 file is in a different location from where the resultant AAC encoded file is saved.  My AAC is saved to %My Music%\iTunes\iTunes Media\Music\Disturbed\The Sickness.

The original file can now be removed from iTunes if you are not using iTunes to manage your MP3s.

Now, delete the newly created AAC encoded file from the iTunes library.  Make sure you answer “Keep File” when iTunes asks if you want to move the song to the recycle bin or keep it.

Remove from Library

Keep file

Now, rename the file extension from M4A to M4R.  Then import THAT file into iTunes.  Now it should show up as a ringtone.  New Ringtone

Ensure that iTunes is set up to sync ringtones, then, sync your iPhone.

Sync Ringtone

Now on your iPhone, go to Settings, Sounds, Ringtone, and select your newly upload ringtone.

Mp3Ringtone10 Mp3Ringtone11 Mp3Ringtone12

Now call yourself and enjoy.

Comment on this
Mike's Blog | Development

Better Toodledo iCal

Saturday, 12 September 2009 05:15 PM
by Coose

UPDATE: This post is outdated.  See this post for an updated version.

Since the iPhone doesn’t have tasks at all, I had to find an external service to handle this.  I started with Remember-the-Milk, but was thoroughly unimpressed.  I stumbled across Toodledo and liked what they had to offer.  What I liked was:

  • Web access
  • Native iPhone application
  • Automatic and nearly transparent sync from iPhone back to web service
  • ICS feed for my Google and iPhone calendars

The last item is what I’m going to talk about today.  When using Windows Mobile, I DX’d the built-in calendar for Pocket Informant, then later for Agenda One.  One of the things I liked the best about those PIMs was that on the current day, it showed tasks that are due today as well as those that are overdue.  That last part is important.  If I had a task due four days ago, and I didn’t do it and check it off, I need to see it on my calendar TODAY.  Not when it was originally due.  Again, this is only true for OVERDUE tasks.  This functionality was not in Toodledo. :(

Toodledo has an ICS feed, which is great.  It allows me to show tasks on my Google calendar, as well as on my iPhone calendar.  They are not ICS task items, they are done as all-day events.  That’s cool because iPhone’s iCal and Google calendar don’t display ICS tasks correctly anyway.  BUT, Toodledo just uses the raw due date on the task for the start and end date of the ICS event.  This has been driving me nuts, so I decided to make a change myself.

My first attempt was to access the Toodledo APIs over the wire directly, and generate my own ICS feed based on the raw tasks.  I created a C# library, that I was also using for a Silverlight Toodledo interface that I’ve been tinkering with.  But I do have a handful of kids, and a full time job, so I decided on an easier way.

I figured that Toodledo is already generating an ICS feed, so let’s take their feed, and for any start and end date that is older than today, just switch it to today.  That way all overdue tasks will continue to show up on today’s date, whatever day today is.  Now I have a more constant reminder to get those overdue tasks DONE!!!

So, I created a new class in my own web site’s App_Code directory that derives from IHttpHandler.  Register that handler in the web.config of my web site for a specific file name, for example ‘mytasks.ics’ (don’t try to access that on my web site, that’s not what I really called it).  The handler uses a WebClient class to retrieve the data from Toodledo, then a regular expression to find expired dates and replace them with today.  I then write that data to the response stream, and viola!

Here’s my class:

    1 using System;

    2 using System.Collections.Generic;

    3 using System.Linq;

    4 using System.Web;

    5 using System.Net;

    6 using System.Text;

    7 using System.Text.RegularExpressions;

    8 using System.Globalization;

    9 using System.IO;

   10 

   11 namespace Funkymule

   12 {

   13     public class ToodledoHandler : IHttpHandler

   14     {

   15         #region IHttpHandler Members

   16 

   17         public bool IsReusable

   18         {

   19             get { return true; }

   20         }

   21 

   22         public void ProcessRequest(HttpContext context)

   23         {

   24             WebClient client = new WebClient();

   25             byte[] buffer = client.DownloadData("http://www.toodledo.com/path/to/your/toodledoo/feed.ics");

   26             StringBuilder ics = new StringBuilder(Encoding.Default.GetString(buffer));

   27 

   28             Regex rx = new Regex(@"^(DTSTART|DTEND);VALUE=DATE:(?<date>.*)\s*$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Multiline);

   29             foreach (Match m in rx.Matches(ics.ToString()))

   30             {

   31                 Group g = m.Groups["date"];

   32                 DateTime endDate = DateTime.ParseExact(g.Value, "yyyyMMdd", CultureInfo.InvariantCulture);

   33                 if (endDate < DateTime.Now)

   34                 {

   35                     ics.Remove(g.Index, g.Length);

   36                     ics.Insert(g.Index, DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture));

   37                 }

   38             }

   39 

   40             context.Response.ContentType = client.ResponseHeaders[HttpResponseHeader.ContentType];

   41             context.Response.Output.Write(ics.ToString());

   42         }

   43 

   44         #endregion

   45     }

   46 }

I put this in my App_Code directory, then added the handler in the web.config:

<system.webServer>

    <handlers>

        <add name="MyTasks" verb="*" path="mytasks.ics" type="Funkymule.ToodledoHandler" />

    </handlers>

</system.webServer>

If you are using a version of IIS older than 7, your configuration will be slightly different from mine.

Now all lines of the Toodledo issued ICS response that are like this:

DTSTART;VALUE=DATE:20090901
DTEND;VALUE=DATE:20090901

Which says that this task was due on September 1, will be changed to:

DTSTART;VALUE=DATE:20090912
DTEND;VALUE=DATE:20090912

because today is the 12th and the example above is an overdue task.  Now when subscribing to this calendar in whatever iCal type app you want (iCal, Google calendar, iPhone calendar, Outlook, Sunbird/Lightning) any overdue tasks will show up as all-day event on the current day.

Quick and easy, and I didn’t have to write a line of iPhone code to get the iPhone to work the way I like it.  Even though the built-in iPhone calendar blows really bad, the ActiveSync/CalDAV/iCal flexibility is a homerun.

UPDATE: This post is outdated.  See this post for an updated version.

Comment on this
Mike's Blog
|

My iPhone and a new PC

Friday, 11 September 2009 06:56 PM
by Coose

Well, I just finished setting up my new PC, and I installed iTunes.  Everything smooth so far.  Remember, the happy unicorn iTunes does everything and you’ll never have to ever worry about anything.  That’s what the Mac guy on TV says.  Well, again, he’s wrong.

I didn’t have any podcasts, or music, or movies on my phone, so syncing those was no big deal.  I did, however, have pictures and applications installed.  When telling iTunes on the new PC to sync applications and sync pictures, it gave me the warning that everything would be erased from my iPhone.  Well, that’s not good.

I made a backup of my camera roll and let it sync the pictures.  Hey, cool…it didn’t erase all my pictures.  Oh wait…the other folders that were NOT in the camera roll are gone.  Damn…I didn’t back those up.  Can I restore an earlier backup?  Probably, but it’s not really worth it.

So what about my applications?  I don’t want to have to download everything again.  And what about the data that is on the phone…I sure don’t want to lose that.

Supposedly you can select the “Transfer purchases from XXX’s iPhone” from the File menu, and it will update the library with everything on the iPhone.  The problem with that strategy is that it doesn’t work.  After Googling for a while, I tried a trick to download a new free app from the app store using iTunes on the PC.  Then tried the transfer function again.  Well, we’re getting closer.  Now there are 4 pages of my apps in iTunes…but where’s the rest?  They are not all there.

If i ever get this figured out, I’ll post my solution here.

UPDATE:

Hilarious.  All my apps that start with the letter P-Z are not included in the “Transfer purchases…” functionality.  WTF Apple?

UPDATE:

Well, opening up my iTunes library mobile applications folder (C:\Users\Michael\Music\iTunes\iTunes Media\Mobile Applications), I could see that the apps starting with the letter P or greater were not there, so I kept that folder open, and kept hitting the “Transfer purchases…” option, watching them slowly start to appear.  Once I verified they were all there, I was able to select the “Sync Applications” checkbox in iTunes, and check all the applications, and was finally able to get a good sync.  I have no idea what caused that, or what fixed that.  But I sure don’t want to go through that again. :(

Comment on this
Mike's Blog | Development

Stuck message in iPhone Gmail draft folder

Wednesday, 02 September 2009 03:32 PM
by Coose

Now this was irritating.  I tried to send a message from my Gmail in the native iPhone mail application.  I fat-fingered the address, so it was undeliverable.  Okay, I can dig it.  But the problem is that I couldn’t get it out of my “Drafts” folder.  Even more irritating, it showed up as 1 draft in the folder, but there was nothing in the folder, just a number (1) next to it.  I rebooted…no love.  I deleted the Gmail account, and recreated it…the dang thing was still there!!!  Now I’m getting annoyed.  But I finally figured it out:

Apparently there are databases in Safari that Gmail uses when using the web app.  This was obviously corrupt.  Under Settings => Safari => Databases, delete the mail.google.com one.  Then for safe measure, I deleted cookies, cache and history.  Then it required a reboot.  Yes, I had to reboot my iPhone…again (still a lot less than I had to reboot my Windows Mobile).

iphone5  iphone1  iphone2  iphone3  iphone4

Viola!  No more ghost messages in Gmail draft folder!

Comment on this
Mike's Blog | Development

Messed up icons on iPhone

Tuesday, 25 August 2009 02:02 PM
by Coose

Unbelievable!  My brand new iPhone is getting the wrong icons on the main screen.  I can’t tell what application is what with the icons mixed up.  That Mac guy on TV told me that only PCs have bugs and that Macs are perfect.  Does the iPhone not fall into this category?  It’s a great phone, but it sure does have it’s problems.

Googling for the problem, I found a lot of people having to uninstall the apps and resync with iTunes.  But they also lose their data, which is not really acceptable.  Restoring an old backup was another solution.  But after hours of restore time, and still losing some data, it only sometimes works?

Then I stumbled across a forum where some guy said to install ANY app, yes, any app, and power off during install. 

No, it doesn’t have to be the app that has the messed up icon.  Just any free app from the iTunes app store.  WHILE IT IS INSTALLING (after it says ‘loading’), hold down the power button and slide the red slider to turn off the power.  Turn it back on, let the app finish installing, and all icons should be restored.

Who the hell thought of that?  Whoever you are out there, thank you, thank you, thank you!  Simple solution to a frustrating problem.

Comment on this
Mike's Blog | Development