Automatically get latest download link from Sparkle in PHP

· by Steve · Read in about 3 min · (511 Words)

This is just a quick post to help someone out on Twitter, and the blog seemed the best place to post it.

If you make Mac apps, you probably use Sparkle to manage your auto-update process, outside the Mac App Store anyway. And so you should, it’s awesome, and makes keeping software up to date much easier. But what about the download link on your website for new users? Wouldn’t it be nice not to have to manually update that every time?

For SourceTree, I use a PHP script which just parses my Sparkle appcast and reads the latest release from there, generating a download link automatically. So whenever I update the Sparkle appcast (and I have scripts to do this from XCode, which I may share in a subsequent post if there’s interest), the download link for new users is always up to date immediately.

The full code is after the jump if you want it. As usual, feel free to use this for whatever you want, but there are no warranties for anything whatsoever; don’t blame me if it doesn’t work, or dissolves your website into a pool of steaming alien slime.

<?php

// We're looking for the latest link link this:
//          <pubDate>Thu, 14 Oct 2010 10:34:03 +0100</pubDate>
//          <enclosure url="http://downloads.whatever.com/MyDownload.dmg" sparkle:version="1.0.0.5"

class AppCastXMLParser
{
   var $tag_name;
   var $tag_data;
   var $tag_prev_name;
   var $tag_parent_name;
   var $latest_date;
   var $use_next_url;
   var $download_url;
   function AppCastXMLParser ()
   {
       $tag_name = NULL;
       $tag_data = array ();
       $tag_prev_name = NULL;
       $tag_parent_name = NULL;

       $latest_date = NULL;
       $use_next_url = true;
       $download_url = NULL;
   }
   function startElement ($parser, $name, $attrs)
   {
      if ($this->tag_name != NULL)
      {
         $this->tag_parent_name = $this->tag_name;
      }
      $this->tag_name = $name;

      if ($name == "enclosure" && $this->use_next_url)
      {
        $this->download_url = $attrs['url'];
        $this->use_next_url = false;
      }

   }
   function endElement ($parser, $name)
   {
      if ($this->tag_name == NULL)
      {
         $this->tag_parent_name = NULL;
      }
      $this->tag_name = NULL;
      $this->tag_prev_name = NULL;
   }
   function characterData ($parser, $data)
   {
      if ($this->tag_name == $this->tag_prev_name)
      {
         $data = $this->tag_data[$this->tag_name] . $data;
      }
      $this->tag_data[$this->tag_name] = $data;
      if ($this->tag_parent_name != NULL)
      {
         $this->tag_data[$this->tag_parent_name . "." . $this->tag_name] = $data;
      }
      $this->tag_prev_name = $this->tag_name;

      if ($this->tag_name == "pubDate")
      {
        if ($thisdate = strtotime($this->tag_data[$this->tag_name]))
        {
            if ($this->latest_date == NULL || $this->latest_date < $thisdate)
            {
                $this->use_next_url = true;
                $this->latest_date = $thisdate;
            }
        }
      }

   }
   function parse ($data)
   {
      $xml_parser = xml_parser_create ();
      xml_set_object ($xml_parser, $this);
      xml_parser_set_option ($xml_parser, XML_OPTION_CASE_FOLDING, false);
      xml_set_element_handler ($xml_parser, "startElement", "endElement");
      xml_set_character_data_handler ($xml_parser, "characterData");
      $success = xml_parse ($xml_parser, $data, true);
      if (!$success)
      {
          $this->tag_data['error'] =  sprintf ("XML error: %s at line %d", xml_error_string(xml_get_error_code ($xml_parser)), xml_get_current_line_number ($xml_parser));
      }
      xml_parser_free ($xml_parser);
      return ($success);
   }
   function getElement ($tag)
   {
      return ($this->tag_data[$tag]);
   }
}

function getLatestDownloadURL()
{

    $appcastparser = new AppCastXMLParser ();

    $appcast = $contents=file_get_contents("http://www.myapp.com/path/to/SparkleAppcast.xml");

    if ($appcastparser->parse ($appcast))
    {
        return $appcastparser->download_url;
    }
    else
    {
        return NULL;
    }
}


$url = getLatestDownloadURL();

if ($url)
{
    echo "<p>Thank you for downloading MyRandomApp, your download should start automatically.</p>";
    echo "<p>If your download doesn't start within 5 seconds, you can use this <a href=\"$url\">direct link</a>.</p>\n";

    echo <<<EXCERPT
    <script type="text/javascript">
    <!--
    function godownload()
    {
        window.location = "$url"
    }
    setTimeout("godownload()", 2000)
    //-->
    </script>
EXCERPT;


}

?>