package AmphetaDesk::MyChannels;
###############################################################################
# AmphetaDesk                                           (c) 2000-2002 Disobey #
# morbus@disobey.com                      http://www.disobey.com/amphetadesk/ #
###############################################################################
# ABOUT THIS PACKAGE:                                                         #
#   This package is important - it carries all of the various functions that  #
#   are used to create and interact with the user's subscription list. By     #
#   default, it comes built in with a number of standard entries, but will    #
#   create a user specific file when the time comes (much like Settings.pm).  #
#                                                                             #
# LIST OF ROUTINES BELOW:                                                     #
#   add_url - adds a channel to myChannels.opml from passed url.              #
#   check_for_duplicate_filename - checks for sub'd filenames existence.      #
#   check_for_duplicate_url - checks for the existance of a sub'd url.        #
#   count_my_channels - simply spits out the number of sub'd channels.        #
#   create_channel_filename - creates a filename based off channel title.     #
#   del_url - delete a single channel from the subscribed list.               #
#   download_my_channels - read our myChannels.opml and get all channels.     #
#   get_my_channels_data - get the value of the passed data from channel url. #
#   get_my_sorted_channels - get arrays of various sorted channel options.    #
#   import_my_channels - imports the OPML data into the current list.         #
#   load_my_channels - reads in the myChannels.opml and returns the data.     #
#   remove_old_channel_files - deletes old and dejected files.                #
#   save_my_channels - saves the relevant channel subscriptions to a file.    #
#   set_my_channels_data - set the value of the passed data for channel url.  #
#   update_my_channel_data - takes a URL and new data and updates our OPML.   #
###############################################################################
#    "Get your hands off my damn remote, woman! Make me some pie! PIEEe!"     #
###############################################################################

use strict; $|++;
use AmphetaDesk::Settings;
use AmphetaDesk::Utilities;
use AmphetaDesk::WWW;
use HTTP::Date qw/time2iso str2time/;
use Time::Local;
use File::Spec::Functions;
use XML::Simple;
require Exporter;
use vars qw( @ISA @EXPORT );
@ISA = qw( Exporter );
@EXPORT = qw( add_url count_my_channels del_url download_my_channels
              get_my_channels_data get_my_sorted_channels import_my_channels 
              load_my_channels remove_old_channel_files save_my_channels
              set_my_channels_data update_my_channel_data );

# where we store our in
# memory myChannels.opml.
my %CHANNELS;

###############################################################################
# add_url - adds a channel to myChannels.opml from passed url.                #
###############################################################################
# USAGE:                                                                      #
#    add_url( $url );                                                         #
#                                                                             #
# NOTES:                                                                      #
#    This routine downloads the passed $url and creates an entry for          #
#    myChannels.opml based on the data it sees within. After saving the file, #
#    it saves the myChannels.opml list.                                       #
#                                                                             #
# RETURNS:                                                                    #
#    1; there's no reason why this routine should fail.                       #
###############################################################################

sub add_url {

   my ($incoming_urls) = @_;
   return unless $incoming_urls;

   # first thing we do is split the URL on any newlines
   # or on ",http://". This allows us to accept mass
   # POSTS of newlined URLs, as well as comma spliced lists.
   my @urls = split(/\n|,http:\/\//, $incoming_urls);

   # now, loop through each one.
   foreach my $url (@urls) {

      # add the http:// back if missing.
      $url =~ s/^/http:\/\// unless $url =~ /^http:\/\//;

      # just some happy blurbage for the log file.
      note("Attempting to add a new channel from $url.");

      # does the url already exists in our subscriptions?
      if ( check_for_duplicate_url($url) ) {
         note("AmphetaDesk has determined that you are already " .
              "subscribed to '$CHANNELS{$url}{title}'.", 1, 1);
         next; # ya boOog! subscribing to the subscribed. sheesh!
      }

      # download the channel data.
      my $channel_raw_data = get($url);
      unless ($channel_raw_data) {
         note("AmphetaDesk could not connect to $url.", 1, 1); next;
      }

      # if this looks like a OPML file, load it in, and import all
      # the various channels within (Radio/AmphetaDesk compatible).
      # we check our return code for success (the import will fail
      # if "xmlurl" attributes don't exist in the OPML.
      if ($channel_raw_data =~ /<opml.*(version)?/i) {
         my $success = import_my_channels($channel_raw_data);
         note("AmphetaDesk has imported the channels from $url and " . 
              "will load them the next time you restart AmphetaDesk.", 1, 1) if $success;
         next; # oooh, super multiple import power! whoOO's house?
      }

      # and suck it in for parsing. module calling is bad.
      my $channel_data = AmphetaDesk::Channels::load_channel($channel_raw_data);
      unless ($channel_data) {
         note("AmphetaDesk could not parse $url.", 1, 1);
         next; # Entities, doctype's, and amperstands, oh my!
      }

      # channel data holders.
      my ($htmlurl, $title, $description);

      # if this is RSS xml, then create a title and description.
      if ( defined( $channel_data->{channel}->{title} ) ) {
         $htmlurl = $channel_data->{channel}->{"link"};
         $title = $channel_data->{channel}->{title};
         $description = $channel_data->{channel}->{description}
            if defined( $channel_data->{channel}->{description} );
      }

      # else, maybe it's scriptingNews...
      elsif ( defined ( $channel_data->{header}->{channelTitle} ) ) {
         $htmlurl = $channel_data->{header}->{channelLink};
         $title = $channel_data->{header}->{channelTitle};
         $description = $channel_data->{header}->{channelDescription}
            if defined( $channel_data->{header}->{channelDescription} );
      }

      # else... 
      else { # we have no clue, so spit an error and return.
         note("AmphetaDesk could not determine the format of $url.", 1, 1); next;
      }

      # sanitize our data before processing.
      $title = strip_newlines_and_tabs($title);
      $description = strip_newlines_and_tabs($description);
      $title = $title || $url; $description = $description || "";

      # now create a filename based off the title.
      my $filename = create_channel_filename( $title );
      my $filepath = catfile( get_setting("dir_data_channels"), $filename );

      # and save the channel data, so we don't have to redownload it.
      open(NEW_CHANNEL, ">$filepath") or
         note("AmphetaDesk could not save the new channel data. " .
              "Please report the following error to " . get_setting("app_email") . ": $!", 1, 1);
      print NEW_CHANNEL $channel_raw_data; close(NEW_CHANNEL);

      # now, add the new channel data to our %CHANNELS.
      $CHANNELS{$url}{date_added}      = time2iso(time);
      $CHANNELS{$url}{date_downloaded} = $CHANNELS{$url}{date_added};
      $CHANNELS{$url}{description}     = $description;
      $CHANNELS{$url}{title}           = $title;
      $CHANNELS{$url}{filename}        = $filename;
      $CHANNELS{$url}{htmlurl}         = $htmlurl;
      $CHANNELS{$url}{xmlurl}          = $url;

      # and save subscriptions to disk.
      save_my_channels();

      # and say hello to our log file.
      note("AmphetaDesk has added '$title' successfully.", 1, 1);
   }

   return 1;

}

###############################################################################
# check_for_duplicate_filename - checks for sub'd filenames existence.        #
# check_for_duplicate_url - checks for the existance of a sub'd url.          #
###############################################################################
# USAGE:                                                                      #
#    check_for_duplicate_filename;                                            #
#    check_for_duplicate_url;                                                 #
#                                                                             #
# NOTES:                                                                      #
#    These routines merely check to see if various filenames or URLs already  #
#    exist in the currently subscribed channels. If so, we return happily.    #
#                                                                             #
# RETURNS:                                                                    #
#    1; a matching filename or url was found.                                 #
#    0; no matching filename or url was found.                                #
###############################################################################

sub check_for_duplicate_url { my ($url) = @_; foreach (keys %CHANNELS) { return 1 if $CHANNELS{$_}{xmlurl} eq $url; } return 0; }
sub check_for_duplicate_filename { my ($filename) = @_; foreach (keys %CHANNELS) { return 1 if $CHANNELS{$_}{filename} eq $filename; } return 0; }

###############################################################################
# count_my_channels - simply spits out the number of sub'd channels.          #
###############################################################################
# USAGE:                                                                      #
#    my $total_count = count_my_channels;                                     #
#                                                                             #
# NOTES:                                                                      #
#    This routine counts the number of channels currently subscribed and      #
#    returns the result. No muss, no fuss. Wheeeeee. My comments own j00.     #
#                                                                             #
# RETURNS:                                                                    #
#    $count; the total number of subscribed channels.                         #
###############################################################################

sub count_my_channels {
   my $total_count; # /me coughs like a sick dog. bah!
   foreach ( values %CHANNELS ) { $total_count++; }
   $total_count; # /me wanders about in a daze, moaning.
}

###############################################################################
# create_channel_filename - creates a filename based off the channel title.   #
###############################################################################
# USAGE:                                                                      #
#    create_channel_filename( $channel_title );                               #
#                                                                             #
# NOTES:                                                                      #
#    This routine looks at $channel_title and creates a semi-unique filename  #
#    based off it. It chalks on ".xml" to the end, and returns the creation.  #
#                                                                             #
# RETURNS:                                                                    #
#    $filename; the semi-unique filename created.                             #
###############################################################################

sub create_channel_filename {

   my ($channel_title) = @_;

   # removing nonword characters,
   # lowercase, chop at 20,
   # and add extension.
   $channel_title =~ s/\W//gi;
   $channel_title = lc($channel_title);
   $channel_title = substr($channel_title, 0, 20);

   # check the %CHANNELS to see if the filename is already
   # in use.  if so, chop the last four chars off the string,
   # and append a number. fixes moreover.com title bug.
   if ( check_for_duplicate_filename("$channel_title.xml") ) {
      $channel_title = substr($channel_title, -4);
      $channel_title .= int(rand 8999)+1000;
   }

   # my logfile is growing larger.
   note("Channel filename has been created: $channel_title.xml.");

   return "$channel_title.xml";

}

###############################################################################
# del_url - delete a single channel from the subscribed list                  #
###############################################################################
# USAGE:                                                                      #
#    del_url( $channel_url );                                                 #
#                                                                             #
# NOTES:                                                                      #
#    This routine takes a look at the passed channel url and sees if there is #
#    a matching one in our global %CHANNELS. If there is, then we delete that #
#    one out of there, and resave our channels file. Multiple urls can be     #
#    specified if they are seperated by ",http://" or a newline.              #
#                                                                             #
# RETURNS:                                                                    #
#    1; there's no reason why this routine would fail.                        #
###############################################################################

sub del_url {

   my ($incoming_urls) = @_;
   return unless $incoming_urls;

   # split the channel url on newline or http://.
   my @urls = split(/\n|,http:\/\//, $incoming_urls);

   # now, loop through each one.
   foreach my $url (@urls) {

      # add the http:// back if missing.
      $url =~ s/^/http:\/\// unless $url =~ /^http:\/\//;

      # go through each of the channel
      # and remove from our $CHANNELS
      # as well as the hard drive.
      if (exists $CHANNELS{$url}) { 
         my $file_location = catfile(get_setting("dir_data_channels"), $CHANNELS{$url}{filename});
         note("AmphetaDesk has removed '$CHANNELS{$url}{title}' successfully.", 1, 1);
         delete $CHANNELS{$url}; unlink $file_location; # listening to chemical brothers.
      } else { note("AmphetaDesk couldn't find $url. Did you already remove it?", 1, 1); }
   }

   # save the channels
   save_my_channels();

   return 1;

}

###############################################################################
# download_my_channels - read our myChannels.opml and get all channels.       #
###############################################################################
# USAGE:                                                                      #
#    download_my_channels();                                                  #
#                                                                             #
# NOTES:                                                                      #
#    This routine takes a look at our loaded %CHANNELS and downloads each     #
#    one to our local directory, assuming it meets a number of time-based     #
#    prerequisities. It will sort the downloads by oldest modified channel.   #
#                                                                             #
# RETURNS:                                                                    #
#    0; if the subscriptions_file could not be loaded.                        #
#    1; all channels were mirrored successfully.                              #
###############################################################################

sub download_my_channels {

   # sort and process each one. the channels are sorted in reverse order,
   # based on the last time they were downloaded, so we loop through each ID.
   my @sorted_channels = get_my_sorted_channels( "date_downloaded", "reversed_urls" );
   foreach my $url (@sorted_channels) {

      # if the downloaded date doesn't exist (because of an import, for example),
      # set yesterday's date (which will force an update). check date_added too.
      if (not $CHANNELS{$url}{date_downloaded} or $CHANNELS{$url}{date_downloaded} eq "") { 
         $CHANNELS{$url}{date_downloaded} = time2iso( time-86400 );
      } $CHANNELS{$url}{date_added} = $CHANNELS{$url}{date_downloaded} unless $CHANNELS{$url}{date_added};

      # if the file exists, then check the timestamps and see if we should download.
      if ( -e catfile( get_setting("dir_data_channels"), $CHANNELS{$url}{filename} ) ) {
 
         # figure out the epoch seconds on the last time the channel was downloaded.
         my $channel_seconds = str2time($CHANNELS{$url}{date_downloaded});

         # if the last time it was downloaded has been less
         # than an hour ago, then we skip the blasted thing.
         # via 'next'. if over an hour, we'll download it.
         # over an hour ago, then we download the file.
         if ((time - $channel_seconds) < 3600) { note("Skipping $CHANNELS{$url}{filename} - local copy is less than an hour old."); next; }
         else { note("Checking $CHANNELS{$url}{filename} - local copy is more than an hour old.", 1); }
      }

      # else, the file doesn't exist, so let ourselves know.
      else { note("Downloading $CHANNELS{$url}{filename} - local copy doesn't exist.", 1); }

      # download and store the file.
      my $result = mirror( $CHANNELS{$url}{xmlurl}, catfile( get_setting("dir_data_channels"), $CHANNELS{$url}{filename} ) );

      # if we did indeed download something, then update our
      # date_downloaded in our %CHANNELS. we'll resave later.
      if ($result) { $CHANNELS{$url}{date_downloaded} =  time2iso(time); }
   }

   # make a pretty divider for our log file and window.
   note("--------------------------------------------------------------------------------", 1);

   # count 'em.
   my $total_count = count_my_channels;
   note("There was a total of $total_count subscribed channels.");
   note("We dance a drunken jig of epic proportions! " . 
           "<shake> <jiggle> <keRAASH!>...") if $total_count > 100;

   # resave the channels list
   # with the latest dates.
   save_my_channels();

   return 1;

}

###############################################################################
# get_my_channels_data - get the value of the passed data from channel url.   #
# set_my_channels_data - set the value of the passed data for channel url.    #
###############################################################################
# USAGE:                                                                      #
#    my $answer = get_my_channels_data( $url, "app_os" );                     #
#    set_setting( $url, "title", "Gamegrene.com" );                           #
#                                                                             #
# NOTES:                                                                      #
#    Returns the value of the passed setting in $url, from $CHANNELS. If      #
#    the passed setting doesn't exist, we return a hash ref of all settings.  #
#    Sets the passed variable to the passed value for $url, in $CHANNELS      #
#                                                                             #
# RETURNS:                                                                    #
#    $value; the value of the passed or set.                                  #
#    undef; if the setting doesn't exist or isn't defined.                    #
###############################################################################

sub get_my_channels_data {
   my ($url, $setting) = @_;
   return undef unless $CHANNELS{$url};
   return $CHANNELS{$url} unless $setting;
   $CHANNELS{$url}{$setting} || undef;
}

sub set_my_channels_data {
   my ($url, $setting, $value) = @_;
   $CHANNELS{$url}{$setting} = $value;
   $CHANNELS{$url}{$setting}; 
}

###############################################################################
# get_my_sorted_channels - get arrays of various sorted channel options.      #
###############################################################################
# USAGE:                                                                      #
#    # returns complete channel data, sorted by title.                        #
#    my @array = get_my_sorted_channels("title", "data");                     #
#                                                                             #
#    # returns a list of arrays, sorted by last downloaded.                   #
#    my @array = get_my_sorted_channels("date_downloaded", "reversed_urls");  #
#                                                                             #
# NOTES:                                                                      #
#    This routine goes through our currently stored subscription list in      #
#    %CHANNELS, and returns an array based on how the data should be sorted   #
#    as well as how the data is returned. Sorting can be done on ANY          #
#    attribute stored in %CHANNELS, and the returned array could be sorted    #
#    normally or reversed, and could contain either URLs of the channels,     #
#    or hashes of the data for the specific channel.                          #
#                                                                             #
# RETURNS:                                                                    #
#    @array; the sorted array of channel ids.                                 #
###############################################################################

sub get_my_sorted_channels {

   my ($sort_by, $return) = @_;

   # prepare our various arrays.
   my @urls = sort { uc( $CHANNELS{$a}{$sort_by} ) cmp uc ( $CHANNELS{$b}{$sort_by} ) } keys %CHANNELS;
   my @urls_r = sort { uc( $CHANNELS{$b}{$sort_by} ) cmp uc ( $CHANNELS{$a}{$sort_by} ) } keys %CHANNELS;
   my @data; foreach (@urls) { push(@data, $CHANNELS{$_}); }
   my @data_r; foreach (@urls_r) { push(@data_r, $CHANNELS{$_}); }

   # return the format requested.
   return @urls if $return eq "urls"; return @urls_r if $return eq "reversed_urls";
   return @data if $return eq "data"; return @data_r if $return eq "reversed_data";

}

###############################################################################
# import_my_channels - imports the OPML data into the current list.           #
# load_my_channels - reads in the myChannels.opml and returns the data.       #
###############################################################################
# USAGE:                                                                      #
#    load_my_channels;                                                        #
#    load_my_channels("/path/to/file");                                       #
#    load_my_channels($string_with_opml);                                     #
#    import_my_channels("/path/to/file");                                     #
#    import_my_channels($string_with_opml);                                   #
#                                                                             #
# NOTES:                                                                      #
#    This routine reads the passed info into %CHANNELS. If this file does not #
#    exist, it will create one in memory based on the defaults. This fixes    #
#    an earlier (pre v0.92) problem where upgrading became a hassle since     #
#    the files could be overwritten. If any changes are made to the default   #
#    channels, they are then saved to disk. The hash this routine creates is  #
#    keyed to the channels xml (see the default hash layout below for an      #
#    example of what it looks like).                                          #
#                                                                             #
#    This routine can accept a local file path, or a string with OPML in it.  #
#                                                                             #
# RETURNS:                                                                    #
#    undef; if the import routine found no applicable XML URLs.               #
#    1; this routine nearly always returns successfully.                      #
###############################################################################

sub import_my_channels { my ($o) = @_; my $rc = load_my_channels($o); save_my_channels(); return $rc; }
sub load_my_channels { # import_my_channels is just a wrapper around this.

   my ($opml) = @_;

   # if we've got data from a file or a string, import.
   if ($opml and ($opml =~ /\n|\f|\r|\t/ or -e $opml)) { 

      # if this isn't a string, open.
      unless ($opml =~ /\n|\f|\r|\t/) { 

         # Radio Userland or similar importing. we load the file into
         # a string first so that we can lowercase xmlUrl and htmlUrl.
         open (OPML, $opml) or note("There was an error opening the OPML file. " .
                                    "Please report the following error to " .
                                    get_setting("app_email") . " : $!", 1);
         local $/ = undef; $opml = <OPML>; close(OPML);
      }

      # does this OPML file have any xmlurl attributes?
      unless ($opml =~ / xmlurl=/i) { note("AmphetaDesk could not find any " . 
                                            "XML URLs within the loaded OPML file.", 1, 1);
                                            return undef; }

      # our data is loaded in (either from the file, or as
      # passed), so we do a innerUppers conversion to lowercase.
      $opml =~ s/xmlUrl/xmlurl/g; $opml =~ s/htmlUrl/htmlurl/g;

      # load the data into a temporary data structure.
      note("Loading and parsing the channels from the OPML file.");
      my $temp_data; eval { $temp_data = XMLin( $opml, keyattr=>{ outline => "+xmlurl" } ) };
      if ($@) { $@ =~ s/[\r\n\f]//g; note("There was an error loading the OPML data. " .
                                          "Please report the following error to " .
                                          get_setting("app_email") . " : $@.", 1); }

      # does the file only have one channel? if so, our structure
      # doesn't contain an array (which we assume below in our foreach),
      # so we've got to treat it specially here. ah well. silly code.
      if ($temp_data->{body}->{outline}->{xmlurl}) { 

         # Radio Userland or similar importing.
         unless ( $temp_data->{body}->{outline}->{filename} ) {
            $temp_data->{body}->{outline}->{filename} =
               create_channel_filename($temp_data->{body}->{outline}->{title});
         } unless ( $temp_data->{body}->{outline}->{date_added} ) {
           $temp_data->{body}->{outline}->{date_added} = time2iso( time ); }
           # set the date_added iso. date downloaded will be set on next dl.

         # and add the channel to our master structure, going 
         # through each hash entry and adding them manually. 
         # we do this so we don't clobber/delete existing attributes.
         my $smaller_hashkey = $temp_data->{body}->{outline}->{xmlurl};
         foreach my $key ( keys %{ $temp_data->{body}->{outline} } ) {
            $CHANNELS{ $smaller_hashkey }->{$key} = $temp_data->{body}->{outline}->{$key};
         } return 1; # we return early so we don't trip our foreach.
      }

      # take the temporary data structure and
      # copy it into the right %CHANNELS structure.
      foreach my $channel ( values %{ $temp_data->{body}->{outline} } ) {

         # Radio Userland or similar importing.
         unless ( $channel->{filename} ) {
            $channel->{filename} = create_channel_filename($channel->{title});
         } unless ( $channel->{date_added} ) { $channel->{date_added} = time2iso( time ); }

         # and add the channel to our master structure, going 
         # through each hash entry and adding them manually. 
         # we do this so we don't clobber/delete existing attributes.
         foreach my $key ( keys %{ $channel } ) {
            $CHANNELS{ $channel->{xmlurl} }->{$key} = $channel->{$key};
         }
      }

      return 1;
   }

   # if $opml wasn't passed,
   # then load our defaults.
   else {
      note("User channels configuration doesn't exist. Using defaults.");
      %CHANNELS = (
        "http://www.disobey.com/amphetadesk/news.xml" => {
          date_added      => "2002-03-01 00:00:00",
          date_downloaded => "2002-03-01 00:00:00",
          description     => "The latest news, updates, and press mentions of Disobey.com's AmphetaDesk.",
          filename        => "amphetadeskdocumenta.xml",
          htmlurl         => "http://www.disobey.com/amphetadesk/",
          title           => "Disobey.com's AmphetaDesk - News and Updates",
          xmlurl          => "http://www.disobey.com/amphetadesk/news.xml"
        },
        "http://www.moreover.com/cgi-local/page?index_naturalhealth+rss" => {
          date_added      => "2002-03-01 00:00:00",
          date_downloaded => "2002-03-01 00:00:00",
          description     => "Consumer: top stories - news headlines from around the web, refreshed every 15 minutes.",
          filename        => "consumernaturalhealt.xml",
          htmlurl         => "http://www.moreover.com/",
          title           => "Consumer: natural health news",
          xmlurl          => "http://www.moreover.com/cgi-local/page?index_naturalhealth+rss"
        },
        "http://www.gamegrene.com/rss/gamegrene.xml" => {
          date_added      => "2002-03-01 00:00:00",
          date_downloaded => "2002-03-01 00:00:00",
          description     => "Filled with unique and interesting computer, tabletop, card, and roleplaying game columns, as well as daily news from the gaming community, Gamegrene.com is the perfect choice for the gamer who's sick of the typical.",
          filename        => "gamegrenecom.xml",
          htmlurl         => "http://www.gamegrene.com/",
          title           => "Game Green? Games Phobia? Gamegrene.com!",
          xmlurl          => "http://www.gamegrene.com/rss/gamegrene.xml"
        },
        "http://www.lockergnome.com/lockergnome.xml" => {
          date_added      => "2002-03-01 00:00:00",
          date_downloaded => "2002-03-01 00:00:00",
          description     => "Free Technology E-mail Newsletters - Packed with 32-bit downloads (freeware / shareware), MP3s, multimedia, fonts, updates, tips, tricks, computer news, and more! We cater to curious users.",
          filename        => "lockergnome.xml",
          htmlurl         => "http://www.lockergnome.com/",
          title           => "Lockergnome",
          xmlurl          => "http://www.lockergnome.com/lockergnome.xml"
        },
        "http://www.researchbuzz.com/researchbuzz.rss" => {
          date_added      => "2002-03-01 00:00:00",
          date_downloaded => "2002-03-01 00:00:00",
          description     => "News and information on search engines and other Internet research resources.",
          filename        => "researchbuzz.xml",
          htmlurl         => "http://www.researchbuzz.com/",
          title           => "ResearchBuzz",
          xmlurl          => "http://www.researchbuzz.com/researchbuzz.rss"
        },
        "http://www.reutershealth.com/eline.rss" => {
          date_added      => "2002-03-01 00:00:00",
          date_downloaded => "2002-03-01 00:00:00",
          description     => "A daily look at the top consumer-oriented health-related news stories.",
          filename        => "reutershealtheline.xml",
          htmlurl         => "http://www.reutershealth.com/",
          title           => "Reuters Health eLine",
          xmlurl          => "http://www.reutershealth.com/eline.rss"
        }
      );

      # now, save the subscriptions to disk.
      save_my_channels();
   }

   return 1;

}

###############################################################################
# remove_old_channel_files - deletes old and dejected files.                  #
###############################################################################
# USAGE:                                                                      #
#    remove_old_channel_files;                                                #
#                                                                             #
# NOTES:                                                                      #
#    A clean up routine run at the beginning of each Ampheta start that will  #
#    remove any extra files that are no longer needed (channels, etc.). This  #
#    really shouldn't be necessary anymore, but can occur with third party    #
#    modification of the myChannels.opml without regard for deleting          #
#    orphaned/stored channel data.                                            #
#                                                                             #
# RETURNS:                                                                    #
#    0; if the channels directory could not be opened.                        #
#    1; if everything happened successfully.                                  #
###############################################################################

sub remove_old_channel_files {

   # say hello and load myChannels.opml.
   note("Checking for orphaned channel files to delete...");

   # open the directory and suck in the files.
   opendir(CHANNELS , get_setting("dir_data_channels")) or return 0;
   my @directory_files = grep !/^\.\.?$/, readdir(CHANNELS); closedir(CHANNELS);

   # now go through each file and each
   # subscription and make sure they match up.
   foreach my $directory_file ( @directory_files ) {

      # if the subscription matches the directory file,
      # then it should be there and we last; out of it.
      my $match; foreach my $channel_url ( keys %CHANNELS ) {
         if ( $directory_file eq $CHANNELS{$channel_url}{filename} ) { $match++; last;  }
      }

      # if there was no match, however, it's a file
      # we don't think should be there, so we delete it.
      unless ( $match ) { note("Condemning \"$directory_file\" back to the grave.");
         unlink( catfile(get_setting("dir_data_channels"), $directory_file) )
            or note("AmphetaDesk couldn't delete $directory_file. Please " .
                    "report the following error to " . get_setting("app_email") . ": $!");
      }

      $match = 0;
   }

   # make a pretty divider for our log file.
   note("--------------------------------------------------------------------------------");

   return 1;

}

###############################################################################
# save_my_channels - saves the relevant channel subscriptions to a file.      #
###############################################################################
# USAGE:                                                                      #
#    save_my_channels;                                                        #
#    save_my_channels("/path/to/file");                                       #
#                                                                             #
# NOTES:                                                                      #
#    This routine merely takes the current %CHANNELS, extracts the channel    #
#    information that should be saved, and then dumps it to the user's        #
#    data directory (or the passed file location). It will be read in on      #
#    successive loads.                                                        #
#                                                                             #
# RETURNS:                                                                    #
#    1; if the new channels were saved and activated.                         #
#    0; if we couldn't save the settings to the passed file name.             #
###############################################################################

sub save_my_channels {

   my ($passed_path) = @_;

   # just some happy blurbage for the log file.
   note("Creating a brand new channels file from the current subscriptions.");

   # create an outputtable settings structure.
   my $temp_data; # used for temporary storage.
   foreach my $channel ( values %CHANNELS ) {
      push @{ $temp_data->{body}->{outline} }, $channel;
   }

   # write out the file. we set noattr for "no attributes", 
   # as well as "opml" as the root name. xmldecl sets the xml
   # declaration to version 1.0...
   eval { XMLout( $temp_data, noattr => 0,
                  rootname => 'opml',
                  xmldecl => "<?xml version=\"1.0\"?>",
                  outputfile => $passed_path || get_setting("files_myChannels"),
                ) } or return 0;

   # die if there are any errors.
   if ($@) { $@ =~ s/[\r\n\f]//g; note("There was an error saving " .
                                       $passed_path || get_setting("names_file_myChannels") . 
                                       ". Please report the following error to "
                                       . get_setting("app_email") . ": $@.", 1); }

   return 1;

}

###############################################################################
# update_my_channel_data - takes a URL and new data and updates our OPML.     #
###############################################################################
# USAGE:                                                                      #
#    update_my_channels_data($url, $parsed_data);                             #
#                                                                             #
# NOTES:                                                                      #
#    This routine looks at the passed $data and uses it to update the OPML    #
#    information for our passed $url. This routine is typically called with   #
#    the data returned from a load_my_channel routine (which indicates a      #
#    successful parse. Incidentally, this is called for every successful      #
#    parse that we do, so that we'll always have the latest info.             #
#                                                                             #
# RETURNS:                                                                    #
#    0; if the passed $url could not be found in our OPML.                    #
#    1; if everything in the channel was updated successfully.                #
###############################################################################

sub update_my_channel_data {

   my ($url, $data) = @_;

   # we're not subscribed to this.
   unless ($CHANNELS{$url}) {
      note("AmphetaDesk couldn't update $url because " .
           "we couldn't find a matching subscription. Please
           report this error to " . get_setting("app_email"));
           return 0;
   }

   # now update each relevant entry from our newly parsed data.
   $CHANNELS{$url}{description} = $data->{channel}{description} if $data->{channel}{description};
   $CHANNELS{$url}{htmlurl}     = $data->{channel}{"link"} if $data->{channel}{"link"};
   $CHANNELS{$url}{title}       = $data->{channel}{title} if $data->{channel}{title};

   # for email, we try going from best to worst.
   $CHANNELS{$url}{email} = $data->{channel}{managingEditor} || $data->{channel}{webMaster} ||
                            $data->{header}{webMaster} || $data->{header}{managingEditor} || "";

   return 1;

}

1;
