hacker has asked for the wisdom of the Perl Monks concerning the following question:

I'm working on refactoring how we present releases on our site for downloads. Right now, we have 12 separate packages for each release, in stable and unstable formats. That's 24 separate packages, and we provide them in .zip and .bz2 formats, and 2 of them are in .rpm format (source and runtime). This makes for 44 packages total, at every release. The packages are separated into docs, extras, viewer, and so on.

We provide .torrent downloads for each of these as well, for both the .zip, .bz2, and .rpm files, and we crank out new releases roughly every month. It gets pretty busy around release time. That brings our total number of links to edit/create to 88 for each release cycle. Add the need to create 5 links for every file for local, torrent, and mirrors, and it gets unweildly.

That being said, I'd like to find a better way to present these on the website. We have several community users who have provided some of their own webspace/bandwidth for mirrors, and on our download page, we have links for each of these packages, per-mirror, per-version (stable vs. unstable). It gets ungainly when a new release is made, having to update the versions, titles, descriptions and so on.

Enter.. HASHES! I'm really horrible at hashes, and I try to stay away from them as much as possible, but I think it is now time for me to give in and learn them.

What I need some ideas for, is the best way to present these downloads, in a logical, scalable way, on our download page, in perl.

I've begun reducing the mirror links by putting them into an array, and randomizing across them, like this:

my $stable = "1.8"; my $unstable = "1.7.2"; my @mirrors = ('http://www.foo.com/bar/stable', 'http://stable.example.com', 'http://downloads.site.org/$stable', 'http://www.unmetered.org/data', ); print "Mirror: $mirrors[rand @mirrors]";

This works of course, and reduces 4 lines of links per-package down to one.

The next problem.. I need a way to roll through the "types" of packages we offer, and then output the link, title, size, and so on, dynamically, per-release (stable vs. unstable). So far, what I have looks like this (thanks to diotalevi for helping me figure most of this out):

my @releases = ( { name => 'Runtime RPM', desc => 'Includes viewer and Python distiller', versions => [{ version => $stable, files => [ "foo-$stable-1.i386.rpm"] }] # versions }, #name { name => 'Source RPM', desc => 'Includes viewer and Python distiller', versions => [{ version => $unstable, files => [ "foo-$stable-1.src.rpm"] }] # versions }, # name { name => 'Viewer in English', desc => '', versions => [{ version => $unstable, files => [ "foo-$unstable.torrent", "foo-$unstable.zip", "foo-$unstable.tar.bz2"] }] # versions }, # name ); foreach my $release (@releases) { my $versions = $release->{'versions'}; foreach my $version (@$versions) { my $files = $version->{'files'}; my $mirror = $mirrors[rand @mirrors]; foreach my $file (@$files) { print "$release->{'desc'}\n\t"; print "$version->{'version'}\n\t"; print "$file\n\t"; print "Mirror: $mirror/$file\n\n"; } } }

The problem with this piece.. is that there is no clear separation of unstable vs. stable, nor is there an easy way to tell it to print them in a specific order. Let me explain:

I would like to call this code, and pass it two parameters, like "Documentation, stable", and then have it return the values which match, so I can foreach through them, as above, for that "type" of download. I would then call it again for "RPM", "Viewer", and so on.

Also, when printing the "unstable" downloads, I should be able to just call that (function?) over and over, passing it an array of values or something (not sure how to word this properly to describe what I'm conceiving).

Can I pass it something like:

my @types = ('Runtime RPM', 'Source RPM', 'Viewer', 'Distiller', 'Documentation', 'Source', 'Extras');

..and have it "walk" across that array, and for each value found, look it up in the hash that contains the corresponding name/package, and print the values found (name, description, file, etc.)

Did that make sense? I'm sure there are a few ways to do this. I'm interested in hearing them all.

Replies are listed 'Best First'.
Re: A Hash is a Hash (of course, of course)
by NetWallah (Canon) on May 23, 2004 at 05:14 UTC
    The contents of your hashes increasingly looking like they belong in a database, and if stored as one, the answers to your questions are all database queries.

    Please consider something like SQLite, which is a single-file easy-to-use database in ONE module. This will separate the data from your program, making it a lot more scalable, and easier to maintain (besides solving your current querying problems).

    Yes - there will be a learning curve, but, believe me, you are header in that direction anyway.

    If your organization already uses a database technology, you may prefer using that over SQLite.

    Offense, like beauty, is in the eye of the beholder, and a fantasy.
    By guaranteeing freedom of expression, the First Amendment also guarntees offense.
Re: A Hash is a Hash (of course, of course)
by eric256 (Parson) on May 23, 2004 at 05:50 UTC
    For the second part of your question (well the stable/unstable part) I would just add a feild along side name and desc that was stable => 1 or 0 depending on weather or not its stable. Then its just a simple grep of the keys. my @stable = grep { $_->{stable} } @releases ; my $unstable = grep { ! $_->{stable} } @releases ; I have to agree this looks like something you should consider putting in a database. Unless you are building a nice interface to maintain the hashes too. :)

    ___________
    Eric Hodges
Re: A Hash is a Hash (of course, of course)
by exussum0 (Vicar) on May 23, 2004 at 17:10 UTC
    Enter.. HASHES! I'm really horrible at hashes, and I try to stay away from them as much as possible, but I think it is now time for me to give in and learn them.
    On the user end, your end, the only difference between a hash and an array, is that one is ordered and uses number keys, and the other uses arbitrary key values. A key value being what goes in between the [] or {}. If you are good with arrays, you are good with hashes, so don't be too hesitant. Oh, and hashes can autovivify. Checking for $x{$y} creates the key,value pair $y,undef, so you'd use exists($x{$y}) instead. A minor thing.

    Under the hood is where it starts to get complex, such as auto vivication, what they key really represents: an index or an arbitrary value which gets converted into a "unique" value etc. (you can have collisions, but they are uber rare)

    The same problems you have with hashes, can occur in arrays, i.e. how you nest them and what's in nesting them.

      Simply checking for $x{$y} does not create a $y key in %x. You have to assign to it, or it has to be part of a subsequent dereference, such as $x{$y}[4]

Re: A Hash is a Hash (of course, of course)
by graff (Chancellor) on May 23, 2004 at 22:46 UTC
    That brings our total number of links to edit/create to 88 for each release cycle. Add the need to create 5 links for every file for local, torrent, and mirrors, and it gets unweildly.

    That being said, I'd like to find a better way to present these on the website.

    So there are 88 distinct files for customers to choose from, and they can take one or more of these from at least five different servers? Sounds like, rather than putting all those links directly into a page, you'd rather put up a form with some combination of listbox(es), check-buttons and/or radio buttons to let the customer see the choices in a compact layout.

    For example, you could start with a listbox for the user to choose which server they prefer. Then a set radio buttons to establish the OS, and another set for download format (torrent/rpm/bz2, etc. -- the choices might need to have explanations to indicate which OS choices they're valid for). Finally, a set of check-box buttons (user can turn on one or more) to indicate which particular apps/versions they want.

    Then it's a matter of assembling the posted information from the form into the corresponding link targets, and putting up a simple page that parrots back the user's choices in human-readable format, together with the set of tailored links ("Here are the links to the items you requested...").

Re: A Hash is a Hash (of course, of course)
by revdiablo (Prior) on May 24, 2004 at 02:31 UTC
    nor is there an easy way to tell it to print them in a specific order

    There certainly is. Your outer data structure is an array, which holds a bunch of hashrefs. The key/value pairs in the hashrefs are unordered, but the array holding hashrefs maintains order. Whichever way you put them in will be the same way they come out. The same goes with your list of versions under each release. This is an array full of hashrefs. You can prove it to yourself with some code like the following:

    my @releases = ( { number => "one" }, { number => "two" }, { number => "three" }, ); print $_->{number}, "\n" for @releases;
Re: A Hash is a Hash (of course, of course)
by parv (Parson) on May 25, 2004 at 23:23 UTC

    Why $releases[ $index ]->{'versions'} is an array reference wrapper around the hash reference, not just straight hash reference?

    # Why this ... my @releases = ( { ... versions => [{ version => $stable, files => [ "foo-$stable-1.i386.rpm"] }] } # ... not this? my @releases = ( { ... versions => { version => $stable, files => [ "foo-$stable-1.i386.rpm"] } } );