1package AmphetaDesk::AmphetaDesk::MyChannels; 2############################################################################### 3# AmphetaDesk::AmphetaDesk (c) 2000-2002 Disobey # 4# morbus@disobey.com http://www.disobey.com/amphetadesk/ # 5############################################################################### 6# ABOUT THIS PACKAGE: # 7# This package is important - it carries all of the various functions that # 8# are used to create and interact with the user's subscription list. By # 9# default, it comes built in with a number of standard entries, but will # 10# create a user specific file when the time comes (much like Settings.pm). # 11# # 12# LIST OF ROUTINES BELOW: # 13# add_url - adds a channel to myChannels.opml from passed url. # 14# check_for_duplicate_filename - checks for sub'd filenames existence. # 15# check_for_duplicate_url - checks for the existance of a sub'd url. # 16# count_my_channels - simply spits out the number of sub'd channels. # 17# create_channel_filename - creates a filename based off channel title. # 18# del_url - delete a single channel from the subscribed list. # 19# download_my_channels - read our myChannels.opml and get all channels. # 20# get_my_channels_data - get the value of the passed data from channel url. # 21# get_my_sorted_channels - get arrays of various sorted channel options. # 22# import_my_channels - imports the OPML data into the current list. # 23# load_my_channels - reads in the myChannels.opml and returns the data. # 24# remove_old_channel_files - deletes old and dejected files. # 25# save_my_channels - saves the relevant channel subscriptions to a file. # 26# set_my_channels_data - set the value of the passed data for channel url. # 27# update_my_channel_data - takes a URL and new data and updates our OPML. # 28############################################################################### 29# "Get your hands off my damn remote, woman! Make me some pie! PIEEe!" # 30############################################################################### 31 32use strict; $|++; 33use AmphetaDesk::AmphetaDesk::Settings; 34use AmphetaDesk::AmphetaDesk::Utilities; 35use AmphetaDesk::AmphetaDesk::WWW; 36use HTTP::Date qw/time2iso str2time/; 37use Time::Local; 38use File::Spec::Functions; 39use AmphetaDesk::XML::Simple; 40require Exporter; 41use vars qw( @ISA @EXPORT ); 42@ISA = qw( Exporter ); 43@EXPORT = qw( add_url count_my_channels del_url download_my_channels 44 get_my_channels_data get_my_sorted_channels import_my_channels 45 load_my_channels remove_old_channel_files save_my_channels 46 set_my_channels_data update_my_channel_data ); 47 48# where we store our in 49# memory myChannels.opml. 50my %CHANNELS; 51 52############################################################################### 53# add_url - adds a channel to myChannels.opml from passed url. # 54############################################################################### 55# USAGE: # 56# add_url( $url ); # 57# # 58# NOTES: # 59# This routine downloads the passed $url and creates an entry for # 60# myChannels.opml based on the data it sees within. After saving the file, # 61# it saves the myChannels.opml list. # 62# # 63# RETURNS: # 64# 1; there's no reason why this routine should fail. # 65############################################################################### 66 67sub add_url { 68 69 my ($incoming_urls) = @_; 70 return unless $incoming_urls; 71 72 # first thing we do is split the URL on any newlines 73 # or on ",http://". This allows us to accept mass 74 # POSTS of newlined URLs, as well as comma spliced lists. 75 my @urls = split(/\n|,http:\/\//, $incoming_urls); 76 77 # now, loop through each one. 78 foreach my $url (@urls) { 79 80 # add the http:// back if missing. 81 $url =~ s/^/http:\/\// unless $url =~ /^http:\/\//; 82 83 # just some happy blurbage for the log file. 84 note("Attempting to add a new channel from $url."); 85 86 # does the url already exists in our subscriptions? 87 if ( check_for_duplicate_url($url) ) { 88 note("AmphetaDesk has determined that you are already " . 89 "subscribed to '$CHANNELS{$url}{title}'.", 1, 1); 90 next; # ya boOog! subscribing to the subscribed. sheesh! 91 } 92 93 # download the channel data. 94 my $channel_raw_data = get($url); 95 unless ($channel_raw_data) { 96 note("AmphetaDesk could not connect to $url.", 1, 1); next; 97 } 98 99 # if this looks like a OPML file, load it in, and import all 100 # the various channels within (Radio/AmphetaDesk compatible). 101 # we check our return code for success (the import will fail 102 # if "xmlurl" attributes don't exist in the OPML. 103 if ($channel_raw_data =~ /<opml.*(version)?/i) { 104 my $success = import_my_channels($channel_raw_data); 105 note("AmphetaDesk has imported the channels from $url and " . 106 "will load them the next time you restart AmphetaDesk::AmphetaDesk.", 1, 1) if $success; 107 next; # oooh, super multiple import power! whoOO's house? 108 } 109 110 # and suck it in for parsing. module calling is bad. 111 my $channel_data = AmphetaDesk::AmphetaDesk::Channels::load_channel($channel_raw_data); 112 unless ($channel_data) { 113 note("AmphetaDesk could not parse $url.", 1, 1); 114 next; # Entities, doctype's, and amperstands, oh my! 115 } 116 117 # channel data holders. 118 my ($htmlurl, $title, $description); 119 120 # if this is RSS xml, then create a title and description. 121 if ( defined( $channel_data->{channel}->{title} ) ) { 122 $htmlurl = $channel_data->{channel}->{"link"}; 123 $title = $channel_data->{channel}->{title}; 124 $description = $channel_data->{channel}->{description} 125 if defined( $channel_data->{channel}->{description} ); 126 } 127 128 # else, maybe it's scriptingNews... 129 elsif ( defined ( $channel_data->{header}->{channelTitle} ) ) { 130 $htmlurl = $channel_data->{header}->{channelLink}; 131 $title = $channel_data->{header}->{channelTitle}; 132 $description = $channel_data->{header}->{channelDescription} 133 if defined( $channel_data->{header}->{channelDescription} ); 134 } 135 136 # else... 137 else { # we have no clue, so spit an error and return. 138 note("AmphetaDesk could not determine the format of $url.", 1, 1); next; 139 } 140 141 # sanitize our data before processing. 142 $title = strip_newlines_and_tabs($title); 143 $description = strip_newlines_and_tabs($description); 144 $title = $title || $url; $description = $description || ""; 145 146 # now create a filename based off the title. 147 my $filename = create_channel_filename( $title ); 148 my $filepath = catfile( get_setting("dir_data_channels"), $filename ); 149 150 # and save the channel data, so we don't have to redownload it. 151 open(NEW_CHANNEL, ">$filepath") or 152 note("AmphetaDesk could not save the new channel data. " . 153 "Please report the following error to " . get_setting("app_email") . ": $!", 1, 1); 154 print NEW_CHANNEL $channel_raw_data; close(NEW_CHANNEL); 155 156 # now, add the new channel data to our %CHANNELS. 157 $CHANNELS{$url}{date_added} = time2iso(time); 158 $CHANNELS{$url}{date_downloaded} = $CHANNELS{$url}{date_added}; 159 $CHANNELS{$url}{description} = $description; 160 $CHANNELS{$url}{title} = $title; 161 $CHANNELS{$url}{filename} = $filename; 162 $CHANNELS{$url}{htmlurl} = $htmlurl; 163 $CHANNELS{$url}{xmlurl} = $url; 164 165 # and save subscriptions to disk. 166 save_my_channels(); 167 168 # and say hello to our log file. 169 note("AmphetaDesk has added '$title' successfully.", 1, 1); 170 } 171 172 return 1; 173 174} 175 176############################################################################### 177# check_for_duplicate_filename - checks for sub'd filenames existence. # 178# check_for_duplicate_url - checks for the existance of a sub'd url. # 179############################################################################### 180# USAGE: # 181# check_for_duplicate_filename; # 182# check_for_duplicate_url; # 183# # 184# NOTES: # 185# These routines merely check to see if various filenames or URLs already # 186# exist in the currently subscribed channels. If so, we return happily. # 187# # 188# RETURNS: # 189# 1; a matching filename or url was found. # 190# 0; no matching filename or url was found. # 191############################################################################### 192 193sub check_for_duplicate_url { my ($url) = @_; foreach (keys %CHANNELS) { return 1 if $CHANNELS{$_}{xmlurl} eq $url; } return 0; } 194sub check_for_duplicate_filename { my ($filename) = @_; foreach (keys %CHANNELS) { return 1 if $CHANNELS{$_}{filename} eq $filename; } return 0; } 195 196############################################################################### 197# count_my_channels - simply spits out the number of sub'd channels. # 198############################################################################### 199# USAGE: # 200# my $total_count = count_my_channels; # 201# # 202# NOTES: # 203# This routine counts the number of channels currently subscribed and # 204# returns the result. No muss, no fuss. Wheeeeee. My comments own j00. # 205# # 206# RETURNS: # 207# $count; the total number of subscribed channels. # 208############################################################################### 209 210sub count_my_channels { 211 my $total_count; # /me coughs like a sick dog. bah! 212 foreach ( values %CHANNELS ) { $total_count++; } 213 $total_count; # /me wanders about in a daze, moaning. 214} 215 216############################################################################### 217# create_channel_filename - creates a filename based off the channel title. # 218############################################################################### 219# USAGE: # 220# create_channel_filename( $channel_title ); # 221# # 222# NOTES: # 223# This routine looks at $channel_title and creates a semi-unique filename # 224# based off it. It chalks on ".xml" to the end, and returns the creation. # 225# # 226# RETURNS: # 227# $filename; the semi-unique filename created. # 228############################################################################### 229 230sub create_channel_filename { 231 232 my ($channel_title) = @_; 233 234 # removing nonword characters, 235 # lowercase, chop at 20, 236 # and add extension. 237 $channel_title =~ s/\W//gi; 238 $channel_title = lc($channel_title); 239 $channel_title = substr($channel_title, 0, 20); 240 241 # check the %CHANNELS to see if the filename is already 242 # in use. if so, chop the last four chars off the string, 243 # and append a number. fixes moreover.com title bug. 244 if ( check_for_duplicate_filename("$channel_title.xml") ) { 245 $channel_title = substr($channel_title, -4); 246 $channel_title .= int(rand 8999)+1000; 247 } 248 249 # my logfile is growing larger. 250 note("Channel filename has been created: $channel_title.xml."); 251 252 return "$channel_title.xml"; 253 254} 255 256############################################################################### 257# del_url - delete a single channel from the subscribed list # 258############################################################################### 259# USAGE: # 260# del_url( $channel_url ); # 261# # 262# NOTES: # 263# This routine takes a look at the passed channel url and sees if there is # 264# a matching one in our global %CHANNELS. If there is, then we delete that # 265# one out of there, and resave our channels file. Multiple urls can be # 266# specified if they are seperated by ",http://" or a newline. # 267# # 268# RETURNS: # 269# 1; there's no reason why this routine would fail. # 270############################################################################### 271 272sub del_url { 273 274 my ($incoming_urls) = @_; 275 return unless $incoming_urls; 276 277 # split the channel url on newline or http://. 278 my @urls = split(/\n|,http:\/\//, $incoming_urls); 279 280 # now, loop through each one. 281 foreach my $url (@urls) { 282 283 # add the http:// back if missing. 284 $url =~ s/^/http:\/\// unless $url =~ /^http:\/\//; 285 286 # go through each of the channel 287 # and remove from our $CHANNELS 288 # as well as the hard drive. 289 if (exists $CHANNELS{$url}) { 290 my $file_location = catfile(get_setting("dir_data_channels"), $CHANNELS{$url}{filename}); 291 note("AmphetaDesk has removed '$CHANNELS{$url}{title}' successfully.", 1, 1); 292 delete $CHANNELS{$url}; unlink $file_location; # listening to chemical brothers. 293 } else { note("AmphetaDesk couldn't find $url. Did you already remove it?", 1, 1); } 294 } 295 296 # save the channels 297 save_my_channels(); 298 299 return 1; 300 301} 302 303############################################################################### 304# download_my_channels - read our myChannels.opml and get all channels. # 305############################################################################### 306# USAGE: # 307# download_my_channels(); # 308# # 309# NOTES: # 310# This routine takes a look at our loaded %CHANNELS and downloads each # 311# one to our local directory, assuming it meets a number of time-based # 312# prerequisities. It will sort the downloads by oldest modified channel. # 313# # 314# RETURNS: # 315# 0; if the subscriptions_file could not be loaded. # 316# 1; all channels were mirrored successfully. # 317############################################################################### 318 319sub download_my_channels { 320 321 # sort and process each one. the channels are sorted in reverse order, 322 # based on the last time they were downloaded, so we loop through each ID. 323 my @sorted_channels = get_my_sorted_channels( "date_downloaded", "reversed_urls" ); 324 foreach my $url (@sorted_channels) { 325 326 # if the downloaded date doesn't exist (because of an import, for example), 327 # set yesterday's date (which will force an update). check date_added too. 328 if (not $CHANNELS{$url}{date_downloaded} or $CHANNELS{$url}{date_downloaded} eq "") { 329 $CHANNELS{$url}{date_downloaded} = time2iso( time-86400 ); 330 } $CHANNELS{$url}{date_added} = $CHANNELS{$url}{date_downloaded} unless $CHANNELS{$url}{date_added}; 331 332 # if the file exists, then check the timestamps and see if we should download. 333 if ( -e catfile( get_setting("dir_data_channels"), $CHANNELS{$url}{filename} ) ) { 334 335 # figure out the epoch seconds on the last time the channel was downloaded. 336 my $channel_seconds = str2time($CHANNELS{$url}{date_downloaded}); 337 338 # if the last time it was downloaded has been less 339 # than an hour ago, then we skip the blasted thing. 340 # via 'next'. if over an hour, we'll download it. 341 # over an hour ago, then we download the file. 342 if ((time - $channel_seconds) < 3600) { note("Skipping $CHANNELS{$url}{filename} - local copy is less than an hour old."); next; } 343 else { note("Checking $CHANNELS{$url}{filename} - local copy is more than an hour old.", 1); } 344 } 345 346 # else, the file doesn't exist, so let ourselves know. 347 else { note("Downloading $CHANNELS{$url}{filename} - local copy doesn't exist.", 1); } 348 349 # download and store the file. 350 my $result = mirror( $CHANNELS{$url}{xmlurl}, catfile( get_setting("dir_data_channels"), $CHANNELS{$url}{filename} ) ); 351 352 # if we did indeed download something, then update our 353 # date_downloaded in our %CHANNELS. we'll resave later. 354 if ($result) { $CHANNELS{$url}{date_downloaded} = time2iso(time); } 355 } 356 357 # make a pretty divider for our log file and window. 358 note("--------------------------------------------------------------------------------", 1); 359 360 # count 'em. 361 my $total_count = count_my_channels; 362 if ($total_count) { 363 note("There was a total of $total_count subscribed channels."); 364 note("We dance a drunken jig of epic proportions! " . 365 "<shake> <jiggle> <keRAASH!>...") if $total_count > 100; 366 } 367 368 # resave the channels list 369 # with the latest dates. 370 save_my_channels(); 371 372 return 1; 373 374} 375 376############################################################################### 377# get_my_channels_data - get the value of the passed data from channel url. # 378# set_my_channels_data - set the value of the passed data for channel url. # 379############################################################################### 380# USAGE: # 381# my $answer = get_my_channels_data( $url, "app_os" ); # 382# set_setting( $url, "title", "Gamegrene.com" ); # 383# # 384# NOTES: # 385# Returns the value of the passed setting in $url, from $CHANNELS. If # 386# the passed setting doesn't exist, we return a hash ref of all settings. # 387# Sets the passed variable to the passed value for $url, in $CHANNELS # 388# # 389# RETURNS: # 390# $value; the value of the passed or set. # 391# undef; if the setting doesn't exist or isn't defined. # 392############################################################################### 393 394sub get_my_channels_data { 395 my ($url, $setting) = @_; 396 return undef unless $CHANNELS{$url}; 397 return $CHANNELS{$url} unless $setting; 398 $CHANNELS{$url}{$setting} || undef; 399} 400 401sub set_my_channels_data { 402 my ($url, $setting, $value) = @_; 403 $CHANNELS{$url}{$setting} = $value; 404 $CHANNELS{$url}{$setting}; 405} 406 407############################################################################### 408# get_my_sorted_channels - get arrays of various sorted channel options. # 409############################################################################### 410# USAGE: # 411# # returns complete channel data, sorted by title. # 412# my @array = get_my_sorted_channels("title", "data"); # 413# # 414# # returns a list of arrays, sorted by last downloaded. # 415# my @array = get_my_sorted_channels("date_downloaded", "reversed_urls"); # 416# # 417# NOTES: # 418# This routine goes through our currently stored subscription list in # 419# %CHANNELS, and returns an array based on how the data should be sorted # 420# as well as how the data is returned. Sorting can be done on ANY # 421# attribute stored in %CHANNELS, and the returned array could be sorted # 422# normally or reversed, and could contain either URLs of the channels, # 423# or hashes of the data for the specific channel. # 424# # 425# RETURNS: # 426# @array; the sorted array of channel ids. # 427############################################################################### 428 429sub get_my_sorted_channels { 430 431 my ($sort_by, $return) = @_; 432 433 # prepare our various arrays. 434 my @urls = sort { uc( $CHANNELS{$a}{$sort_by} ) cmp uc ( $CHANNELS{$b}{$sort_by} ) } keys %CHANNELS; 435 my @urls_r = sort { uc( $CHANNELS{$b}{$sort_by} ) cmp uc ( $CHANNELS{$a}{$sort_by} ) } keys %CHANNELS; 436 my @data; foreach (@urls) { push(@data, $CHANNELS{$_}); } 437 my @data_r; foreach (@urls_r) { push(@data_r, $CHANNELS{$_}); } 438 439 # return the format requested. 440 return @urls if $return eq "urls"; return @urls_r if $return eq "reversed_urls"; 441 return @data if $return eq "data"; return @data_r if $return eq "reversed_data"; 442 443} 444 445############################################################################### 446# import_my_channels - imports the OPML data into the current list. # 447# load_my_channels - reads in the myChannels.opml and returns the data. # 448############################################################################### 449# USAGE: # 450# load_my_channels; # 451# load_my_channels("/path/to/file"); # 452# load_my_channels($string_with_opml); # 453# import_my_channels("/path/to/file"); # 454# import_my_channels($string_with_opml); # 455# # 456# NOTES: # 457# This routine reads the passed info into %CHANNELS. If this file does not # 458# exist, it will create one in memory based on the defaults. This fixes # 459# an earlier (pre v0.92) problem where upgrading became a hassle since # 460# the files could be overwritten. If any changes are made to the default # 461# channels, they are then saved to disk. The hash this routine creates is # 462# keyed to the channels xml (see the default hash layout below for an # 463# example of what it looks like). # 464# # 465# This routine can accept a local file path, or a string with OPML in it. # 466# # 467# RETURNS: # 468# undef; if the import routine found no applicable XML URLs. # 469# 1; this routine nearly always returns successfully. # 470############################################################################### 471 472sub import_my_channels { my ($o) = @_; my $rc = load_my_channels($o); save_my_channels(); return $rc; } 473sub load_my_channels { # import_my_channels is just a wrapper around this. 474 475 my ($opml) = @_; 476 477 # if we've got data from a file or a string, import. 478 if ($opml and ($opml =~ /\n|\f|\r|\t/ or -e $opml)) { 479 480 # if this isn't a string, open. 481 unless ($opml =~ /\n|\f|\r|\t/) { 482 483 # Radio Userland or similar importing. we load the file into 484 # a string first so that we can lowercase xmlUrl and htmlUrl. 485 open (OPML, $opml) or note("There was an error opening the OPML file. " . 486 "Please report the following error to " . 487 get_setting("app_email") . " : $!", 1); 488 local $/ = undef; $opml = <OPML>; close(OPML); 489 } 490 491 # does this OPML file have any xmlurl attributes? 492 unless ($opml =~ / xmlurl=/i) { note("AmphetaDesk could not find any " . 493 "XML URLs within the loaded OPML file.", 1, 1); 494 return undef; } 495 496 # our data is loaded in (either from the file, or as 497 # passed), so we do a innerUppers conversion to lowercase. 498 $opml =~ s/xmlUrl/xmlurl/g; $opml =~ s/htmlUrl/htmlurl/g; 499 500 # load the data into a temporary data structure. 501 note("Loading and parsing the channels from the OPML file."); 502 my $temp_data; eval { $temp_data = XMLin( $opml, keyattr=>{ outline => "+xmlurl" } ) }; 503 if ($@) { $@ =~ s/[\r\n\f]//g; note("There was an error loading the OPML data. " . 504 "Please report the following error to " . 505 get_setting("app_email") . " : $@.", 1); } 506 507 # does the file only have one channel? if so, our structure 508 # doesn't contain an array (which we assume below in our foreach), 509 # so we've got to treat it specially here. ah well. silly code. 510 if ($temp_data->{body}->{outline}->{xmlurl}) { 511 512 # Radio Userland or similar importing. 513 unless ( $temp_data->{body}->{outline}->{filename} ) { 514 $temp_data->{body}->{outline}->{filename} = 515 create_channel_filename($temp_data->{body}->{outline}->{title}); 516 } unless ( $temp_data->{body}->{outline}->{date_added} ) { 517 $temp_data->{body}->{outline}->{date_added} = time2iso( time ); } 518 # set the date_added iso. date downloaded will be set on next dl. 519 520 # and add the channel to our master structure, going 521 # through each hash entry and adding them manually. 522 # we do this so we don't clobber/delete existing attributes. 523 my $smaller_hashkey = $temp_data->{body}->{outline}->{xmlurl}; 524 foreach my $key ( keys %{ $temp_data->{body}->{outline} } ) { 525 $CHANNELS{ $smaller_hashkey }->{$key} = $temp_data->{body}->{outline}->{$key}; 526 } return 1; # we return early so we don't trip our foreach. 527 } 528 529 # take the temporary data structure and 530 # copy it into the right %CHANNELS structure. 531 foreach my $channel ( values %{ $temp_data->{body}->{outline} } ) { 532 533 # Radio Userland or similar importing. 534 unless ( $channel->{filename} ) { 535 $channel->{filename} = create_channel_filename($channel->{title}); 536 } unless ( $channel->{date_added} ) { $channel->{date_added} = time2iso( time ); } 537 538 # and add the channel to our master structure, going 539 # through each hash entry and adding them manually. 540 # we do this so we don't clobber/delete existing attributes. 541 foreach my $key ( keys %{ $channel } ) { 542 $CHANNELS{ $channel->{xmlurl} }->{$key} = $channel->{$key}; 543 } 544 } 545 546 return 1; 547 } 548 549 # if $opml wasn't passed, 550 # then load our defaults. 551 else { 552 note("User channels configuration doesn't exist. Using defaults."); 553 %CHANNELS = ( 554 "http://www.disobey.com/amphetadesk/news.xml" => { 555 date_added => "2002-03-01 00:00:00", 556 date_downloaded => "2002-03-01 00:00:00", 557 description => "The latest news, updates, and press mentions of Disobey.com's AmphetaDesk::AmphetaDesk.", 558 filename => "amphetadeskdocumenta.xml", 559 htmlurl => "http://www.disobey.com/amphetadesk/", 560 title => "Disobey.com's AmphetaDesk::AmphetaDesk - News and Updates", 561 xmlurl => "http://www.disobey.com/amphetadesk/news.xml" 562 }, 563 "http://www.freebsd.org/news/news.rdf" => { 564 date_added => "2005-10-11 00:00:00", 565 date_downloaded => "2005-10-11 00:00:00", 566 description => "News from the FreeBSD Project", 567 filename => "freebsdprojectnews.xml", 568 htmlurl => "http://www.FreeBSD.org/news/", 569 title => "FreeBSD Project News", 570 xmlurl => "http://www.freebsd.org/news/news.rdf" 571 }, 572 "http://www.freshports.org/news.php" => { 573 date_added => "2005-10-11 00:00:00", 574 date_downloaded => "2005-10-11 00:00:00", 575 description => "he easiest place to find ports", 576 filename => "freshportstheplacefo.xml", 577 htmlurl => "http://www.FreshPorts.org/", 578 title => "FreshPorts -- The Place For Ports", 579 xmlurl => "http://www.freshports.org/news.php" 580 }, 581 "http://www.oreillynet.com/feeds/author/?x-au=73&x-mimetype=application/rdf+xm" => { 582 date_added => "2005-10-11 00:00:00", 583 date_downloaded => "2005-10-11 00:00:00", 584 description => "Dru Lavigne's O'Reilly Network Items: Weblogs, Articles, Hacks and Books", 585 filename => "drulavigneoreillynet.xml", 586 htmlurl => "http://www.oreillynet.com/pub/au/73", 587 title => "Dru Lavigne, O'Reilly Network", 588 xmlurl => "http://www.oreillynet.com/feeds/author/?x-au=73&x-mimetype=application/rdf+xm" 589 }, 590 "http://daily.daemonnews.org/ddn.rdf.php3" => { 591 date_added => "2005-10-11 00:00:00", 592 date_downloaded => "2005-10-11 00:00:00", 593 description => "Daemon News PHP News System", 594 filename => "bsdnews.xml", 595 htmlurl => "http://bsdnews.com", 596 title => "BSD News", 597 xmlurl => "http://daily.daemonnews.org/ddn.rdf.php3" 598 } 599 ); 600 601 # now, save the subscriptions to disk. 602 save_my_channels(); 603 } 604 605 return 1; 606 607} 608 609############################################################################### 610# remove_old_channel_files - deletes old and dejected files. # 611############################################################################### 612# USAGE: # 613# remove_old_channel_files; # 614# # 615# NOTES: # 616# A clean up routine run at the beginning of each Ampheta start that will # 617# remove any extra files that are no longer needed (channels, etc.). This # 618# really shouldn't be necessary anymore, but can occur with third party # 619# modification of the myChannels.opml without regard for deleting # 620# orphaned/stored channel data. # 621# # 622# RETURNS: # 623# 0; if the channels directory could not be opened. # 624# 1; if everything happened successfully. # 625############################################################################### 626 627sub remove_old_channel_files { 628 629 # say hello and load myChannels.opml. 630 note("Checking for orphaned channel files to delete..."); 631 632 # open the directory and suck in the files. 633 opendir(CHANNELS , get_setting("dir_data_channels")) or return 0; 634 my @directory_files = grep !/^\.\.?$/, readdir(CHANNELS); closedir(CHANNELS); 635 636 # now go through each file and each 637 # subscription and make sure they match up. 638 foreach my $directory_file ( @directory_files ) { 639 640 # if the subscription matches the directory file, 641 # then it should be there and we last; out of it. 642 my $match; foreach my $channel_url ( keys %CHANNELS ) { 643 if ( $directory_file eq $CHANNELS{$channel_url}{filename} ) { $match++; last; } 644 } 645 646 # if there was no match, however, it's a file 647 # we don't think should be there, so we delete it. 648 unless ( $match ) { note("Condemning \"$directory_file\" back to the grave."); 649 unlink( catfile(get_setting("dir_data_channels"), $directory_file) ) 650 or note("AmphetaDesk couldn't delete $directory_file. Please " . 651 "report the following error to " . get_setting("app_email") . ": $!"); 652 } 653 654 $match = 0; 655 } 656 657 # make a pretty divider for our log file. 658 note("--------------------------------------------------------------------------------"); 659 660 return 1; 661 662} 663 664############################################################################### 665# save_my_channels - saves the relevant channel subscriptions to a file. # 666############################################################################### 667# USAGE: # 668# save_my_channels; # 669# save_my_channels("/path/to/file"); # 670# # 671# NOTES: # 672# This routine merely takes the current %CHANNELS, extracts the channel # 673# information that should be saved, and then dumps it to the user's # 674# data directory (or the passed file location). It will be read in on # 675# successive loads. # 676# # 677# RETURNS: # 678# 1; if the new channels were saved and activated. # 679# 0; if we couldn't save the settings to the passed file name. # 680############################################################################### 681 682sub save_my_channels { 683 684 my ($passed_path) = @_; 685 686 # just some happy blurbage for the log file. 687 note("Creating a brand new channels file from the current subscriptions."); 688 689 # create an outputtable settings structure. 690 my $temp_data; # used for temporary storage. 691 foreach my $channel ( values %CHANNELS ) { 692 push @{ $temp_data->{body}->{outline} }, $channel; 693 } 694 695 # write out the file. we set noattr for "no attributes", 696 # as well as "opml" as the root name. xmldecl sets the xml 697 # declaration to version 1.0... 698 eval { XMLout( $temp_data, noattr => 0, 699 rootname => 'opml', 700 xmldecl => "<?xml version=\"1.0\"?>", 701 outputfile => $passed_path || get_setting("files_myChannels"), 702 ) } or return 0; 703 704 # die if there are any errors. 705 if ($@) { $@ =~ s/[\r\n\f]//g; note("There was an error saving " . 706 $passed_path || get_setting("names_file_myChannels") . 707 ". Please report the following error to " 708 . get_setting("app_email") . ": $@.", 1); } 709 710 return 1; 711 712} 713 714############################################################################### 715# update_my_channel_data - takes a URL and new data and updates our OPML. # 716############################################################################### 717# USAGE: # 718# update_my_channels_data($url, $parsed_data); # 719# # 720# NOTES: # 721# This routine looks at the passed $data and uses it to update the OPML # 722# information for our passed $url. This routine is typically called with # 723# the data returned from a load_my_channel routine (which indicates a # 724# successful parse. Incidentally, this is called for every successful # 725# parse that we do, so that we'll always have the latest info. # 726# # 727# RETURNS: # 728# 0; if the passed $url could not be found in our OPML. # 729# 1; if everything in the channel was updated successfully. # 730############################################################################### 731 732sub update_my_channel_data { 733 734 my ($url, $data) = @_; 735 736 # we're not subscribed to this. 737 unless ($CHANNELS{$url}) { 738 note("AmphetaDesk couldn't update $url because " . 739 "we couldn't find a matching subscription. Please 740 report this error to " . get_setting("app_email")); 741 return 0; 742 } 743 744 # now update each relevant entry from our newly parsed data. 745 $CHANNELS{$url}{description} = $data->{channel}{description} if $data->{channel}{description}; 746 $CHANNELS{$url}{htmlurl} = $data->{channel}{"link"} if $data->{channel}{"link"}; 747 $CHANNELS{$url}{title} = $data->{channel}{title} if $data->{channel}{title}; 748 749 # for email, we try going from best to worst. 750 $CHANNELS{$url}{email} = $data->{channel}{managingEditor} || $data->{channel}{webMaster} || 751 $data->{header}{webMaster} || $data->{header}{managingEditor} || ""; 752 753 return 1; 754 755} 756 7571; 758