0

This post discusses how to inform the user via a progress bar by calculating the percentage of the file being downloaded by a www::mechanize Perl script.

I need to inform my user of a www::mechanize script's progress but not necessarily in percentage remaining. The script is migrating through a user's account and picking up data, which can vary significantly in size so the percentage factor is unknown.

If I can show "progress" via some js DOM div writing (in the dialog that shows a "loader" image while the perl script is running) then that would suffice.

Can I inject js script like:

<script>
$('#formboxtext').html('Logging into account ...');
</script>

into the Perl script to show progression to my user? Or does the Perl script have to return before the DOM will get updated? (The answer appears to be "no".)

Are JavaScript::SpiderMonkey and WWW::Scripter modules I need to make this happen, or is the solution more simple?

EDIT:

I am expanding a PHP-based CMS. I have written a screen-scraping script using Perl and the module www::Mechanize which traverses several pages of a user's account on another site for retrieval into mySQL databases. I will display the collected content in a php form for the user to save once the Perl script is completed. The collection process will range from 10 seconds to a minute. I would like to display progression as the script navigates the user's account pages collecting information.

To begin, I supply the user a jQuery modal dialog (calling a php file to fill it's contents with a form of username and password inputs) used to login to their personal account. I show a loader image in this dialog and a sentence asking for patience, but I would like to use jQuery to rewrite this sentence (div) as the Perl script navigates through pages, sending back progress to display; such as, "I'm here now. Now I'm over here." If an error occurs (i.e. bad login) then I can rewrite the modal dialog and offer solutions per usual. If success, then I would close the dialog and display the collected information in form inputs ready for saving into my database - on the php page that spawned the modal dialog.

If all of this entails multiple DOMs, forking processes and returning control from one script execution to another... then I am definitely over my head. BUT, I would like to learn it. :) I would appreciate an overview on what to read and learn. If it's actually much simpler than I realize then I would appreciate that answer too.

Thanks to daxim and reinierpost for their patience and advice. Thanks for any help.

The Answer: Recap: For me, I decided to fake the progress bar showing progression by estimating the time it would take. That has worked nicely. This post shows how you can dup output from a perl script back to the calling php script, but then feeding that information back to the original DOM was becoming too complex for it's worth. It was made more complex by various parameters passed to the perl script which changed the progression and output. "Faking it" proved a nice solution. Hey, now I see why my girlfriend does it! :)

PS. I gave the green checkmark to daxim because he has answered other questions of mine and been a big help to me, even though he was sort of shooting in the dark on this one.

Community
  • 1
  • 1
Ricalsin
  • 950
  • 9
  • 28
  • 1
    Which DOM are you referring to? Who is viewing these pages? Are you using `WWW::Mechanize` in a web application? Are you displaying the pages being retrieved to the user? – reinierpost Apr 12 '12 at 16:07
  • I am surprised someone +1 @reinerpost. Is there no "intuition" in programming? How is it then that others comprehend the post perfectly? Isn't the goal of a good post to be short and concise? The proper use of "assumption" can alleviate long-winded explanations, thus saving everyone's time for when a concept truly needs to be framed with non-obvious facts. Or, am I just hemorrhaging theoretical details (and have no friend who finds me worthy of an up count)? – Ricalsin Apr 12 '12 at 18:30
  • Apparently, *two* people didn't understand it. And at least one of them still doesn't. – reinierpost Apr 13 '12 at 08:59
  • You're right, @reinierpost. I'll try to correct my post. Thanks. – Ricalsin Apr 14 '12 at 05:09
  • Show the code for the the scraper so that one can see what the appropriate way for calculating progress from it is. – daxim Apr 14 '12 at 11:55
  • @Ricalsin: Thank you for the explanation! I thought it would be something like this, but I could only guess. – reinierpost Apr 16 '12 at 09:44

1 Answers1

1

Instead of printing progress to STDOUT as in https://stackoverflow.com/a/1938448, expose the number as a Web service. This should suffice:

sub {
    return [200, [Content_Type => 'text/plain'], [$PROGRESS]]
}

On the client side, use jQuery get to poll the Web service every half second or so and jQuery Progressbar to display it.


Edit: code example

use 5.010;
use strictures;
use DBI qw();
use Plack::Request qw();
use POSIX qw(floor);
use Forks::Super qw(fork);
use WWW::Mechanize qw();

sub db_connect {
    return DBI->connect('dbi:SQLite:dbname=/var/tmp/progress.db');
}

sub {
    my ($env) = @_;
    my $req = Plack::Request->new($env);
    if ('/progress' eq $req->path_info) {
        return [200,
            [Content_Type => 'text/html'],
            [db_connect->selectrow_array('select progress from progress')]
        ]
    } else {
        fork(sub => sub {
            state $total = 0;
            my $dbh = db_connect;
            $dbh->do('delete from progress');
            $dbh->do('insert into progress (progress) values (0)');
            WWW::Mechanize->new->get(
                'http://localhost:5000/VBoxGuestAdditions_4.0.4.iso', # large-ish file
                ':read_size_hint' => 1024**2,
                ':content_cb' => sub {
                    my ($data, $response, $proto) = @_;
                    $total += length($data);
                    my $size = $response->header('Content-Length');
                    $dbh->do(
                        'update progress set progress = ?', {}, floor(($total/$size)*100)
                    );
sleep 1;
                },
            );
        }) unless defined db_connect->selectrow_array('select progress from progress');
        return [200,
            [Content_Type => 'text/html'],
            [q~<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.8.18/themes/base/jquery-ui.css" />
<style>
#progress { width: 80em; height: 5em; border: 1px solid black; }
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"></script>
<script>
jQuery(document).ready(function() {
    // TODO: stop the timer if progress == 100 or no response
    var timer = setInterval(function() {
        jQuery.ajax({ async: false, cache: false, dataType: 'html', url: 'http://localhost:5001/progress' }).done(function(progress) {
            jQuery('#progress').progressbar({ value: parseInt(progress) });
        });
    }, 500);
});
</script>
</head>
<body>
<h1>downloading your stuff</h1>
<div id="progress"><div>
</body>
</html>~]
        ]
    }
};
Community
  • 1
  • 1
daxim
  • 39,270
  • 4
  • 65
  • 132
  • Hello @daxim . Once again you have given me unbelievable advice. I've been reading up on your original post and now your "edit: code example" is awesome. I just need some time to study it. Thank you so much! I'll leave the green check mark off (for now) just to solicit other input - though nobody seems to ever address questions so good. Thank you for sharing your knowledge! – Ricalsin Apr 12 '12 at 18:59
  • I've been reading a tutorial on polling with jQuery. Coming back to your example, it seems over my head. I am not downloading a file, rather taking a series of steps. – Ricalsin Apr 12 '12 at 20:21
  • Geez, @daxim, intense code you just dropped on me. :) I've been reading the modules but I'm only just begging to get the idea. Can you give a written overview of what is happening here? – Ricalsin Apr 12 '12 at 21:28
  • I see the jQuery. How is the DBI database connect (is that a tmp file being used as a flat [meaning a text file] database?) and Plack:Request's if ('/progress' eq $req->path_info) relate to the lines: $dbh->do('delete from progress'); $dbh->do('insert into progress (progress) values (0)'); Thanks for any help! – Ricalsin Apr 12 '12 at 21:41
  • [SQLite](http://enwp.org/SQLite) is not a temp file or text file, but a real database. [`connect` in DBI](http://p3rl.org/DBI#connect) returns a database handle to operate on with SQL statements. Read the [DBI book](http://dbi.perl.org/) if you haven't seen a database interface before. Learn SQL basics if you don't know it. - Plack::Request does not relate to this at all. Its purpose is routing depending on the HTTP request: to simply make the distinction between whether to return the progress number for Ajax, or to kick off the download in the background and return the main HTML document. – daxim Apr 13 '12 at 10:16
  • Thanks. I am familiar with php/mySQL as the CMS I am using is built with it. I use Perl to screen-scrape data that is then loaded into the CMS databases. Of course, you didn't know that so you offered a way to do it all in Perl - so it appears @reinierpost is correct in his response (above). I'll change the OP. I apologize. – Ricalsin Apr 14 '12 at 05:07