[EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]> wrote:

: This is basically my 1st perl script.
:
: It's a podcast download app. I don't know the etiquette for
: attaching files, but I have attached a <100 line perl file.
:
: It seems that it fails if there is only 1 podcast for a feed
: with:
:
: fetching http://www.abc.net.au/science/k2/podcast/drk_rss.xml ...
:
: Odd number of elements in anonymous hash
: at /home/lexhider/bin/GodCast.pl line 65.
: fileparse(): need a valid pathname at
: /home/lexhider/bin/GodCast.pl line 68


    The XML::Simple parser will not assign a single item
(<item></item>) to an array reference like it does with many
items (unless you specify it to do so). If $data->{channel}->{item}
is an array reference, you have many items. If it is a hash
reference you have only one item. If the "item" key does not
exist, then there are no items.

if ( exists $data->{channel}->{item} ) {
    # 1 or more items.

} else {
    # No items.
}

    We can check the number of items using the perl ref() function.

if ( exists $data->{channel}->{item} ) {

    # 1 or more items.
    if ( ref $data->{channel}->{item} eq 'HASH' ) {
        # 1 item

    } elsif ( ref $data->{channel}->{item} eq 'ARRAY' ) {
        # More than 1 item.
    }

} else {
    # No items.
}

    Ideally, we would like XML::Simple to return the following.

     - No items     An array with no items in it.
     - One item     An array with only one item in it.
     - Many items   An array with many items in it.

if ( exists $data->{channel}->{item} ) {

    # 1 or more items.
    if ( ref $data->{channel}->{item} eq 'HASH' ) {
        $data->{channel}->{item} = [ $data->{channel}->{item} ];
        # 1 item

    } elsif ( ref $data->{channel}->{item} eq 'ARRAY' ) {
        # more than 1 item.
    }

} else {
    # No items.
    $data->{channel}->{item} = [];
}

    Since the "many items" section doesn't do anything (it's in
the form we want), we can eliminate that choice.

if ( exists $data->{channel}->{item} ) {

    if ( ref $data->{channel}->{item} eq 'HASH' ) {
        # Only 1 item.
        $data->{channel}->{item} = [ $data->{channel}->{item} ];
    }

} else {
    # No items.
    $data->{channel}->{item} = [];
}


    As it turns out, XML::Simple has an option to force arrays
when there is only one item. Now we only need to check on zero
items (though that probably shouldn't happen).

my $xml     = XML::Simple->new();
my $source  = get $feed or die qq(Failed to fetch feed: "$feed".)
my $data    = $xml->XMLin( $source, ForceArray => [ 'item' ] );

# Handle zero items.
$data->{channel}->{item} = [] unless exists $data->{channel}->{item};


    Now that we always have an item array we can tackle the
$latest option. While we can do the testing as you did, it might
be easier if we just limit the size of the item array. Then we can
step through the array without a test each time. Here's a way to
do it using the min() function from List::Util.

use List::Util qw( min );

# Get just the array items we want to process.
if ( $latest ) {
    my $item_count =
        min( $latest, scalar @{ $data->{channel}->{item} } );

    @{ $data->{channel}->{item} } =
        @{ $data->{channel}->{item} }[0 .. $item_count - 1];
}

    And then the loop.

foreach my $item ( @{ $data->{channel}->{item} } ) {

    my $cast = $item->{enclosure}->{url};
    chomp( my $base = basename($cast) );

    if ( ! -e "$feed_dir/$base" ) {
        `wget -c $cast`;
        move( $base, $feed_dir );

    } else {
        print "On HD: $base.\n";
    }
}

    Putting that together we get something like this.
(Not tested.)

use strict;
use warnings;
use Getopt::Long;
use XML::Simple;

# Imported functions
use File::Basename  qw( basename );
use File::Copy;     qw( move );
use List::Util      qw( min );
use LWP::Simple     qw( get );

my $download_dir = "$ENV{HOME}/Audio/Podcasts";
my $config_dir   = "$ENV{HOME}/.GodCast";

# File with url of feeds on each line.
my $feeds = "$config_dir/feeds.txt";

# Change tmp if you want semi-downloaded
# files to stay after a reboot.
my $tmp = "/tmp/Podcasts";

my $latest = 0;
my $across;             # Not implemented.

GetOptions(
    'latest|l=s' => \$latest,
    'across'     => \$across
);

foreach my $dir ( $download_dir, $config_dir, $tmp, ) {
    unless( -d $dir ) {
        mkdir $dir or die qq(Failed to make dir "$dir": $!);
    }
}

die qq(File "$feeds" not readable: $!) unless -r $feeds;

open FEEDS, $feeds or die qq(Could not open file: "$feeds": $!);

while ( my $feed = <FEEDS> ) {
    chomp $feed;
    print "\nfetching $feed ... \n\n";

    my $xml     = XML::Simple->new();
    my $source  = get $feed or die qq(Failed to fetch feed: "$feed".);
    my $data    = $xml->XMLin( $source, ForceArray => [ 'item' ] );

    # Handle zero items.
    $data->{channel}->{item} = [] unless exists $data->{channel}->{item};


    my $feed_dir        = "$download_dir/$data->{channel}->{title}";
    my $temp_feed_dir   = "$tmp/$data->{channel}->{title}";

    foreach my $dir ( $feed_dir, $temp_feed_dir ) {
        unless (-d $dir) {
            mkdir $dir or die qq(Failed to make directory "$dir": $!);
        }
    }

    chdir $temp_feed_dir;

    # Get just the array items we want to process.
    if ( $latest ) {
        my $item_count =
            min( $latest, scalar @{ $data->{channel}->{item} } );

        @{ $data->{channel}->{item} } =
            @{ $data->{channel}->{item} }[0 .. $item_count - 1];
    }

    foreach my $item ( @{ $data->{channel}->{item} } ) {

        my $cast = $item->{enclosure}->{url};
        chomp( my $base = basename($cast) );

        if ( ! -e "$feed_dir/$base" ) {
            `wget -c $cast`;
            move( $base, $feed_dir );

        } else {
            print "On HD: $base\n";
        }
    }
}

close FEEDS;

__END__




HTH,


Charles K. Clarkson
-- 
Mobile Homes Specialist
254 968-8328


-- 
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
<http://learn.perl.org/> <http://learn.perl.org/first-response>


Reply via email to