in reply to Re^3: file upload and IO::Handle
in thread file upload and IO::Handle

You should also try die ref $lightweight_fh to be sure it's not an object that stringifies to the filename. If it is not an object, it cannot use methods (->handle). If it is, you should read the documentation of its class to see what methods are supported. Seems like a bug in the CGI documentation?

Replies are listed 'Best First'.
Re^5: file upload and IO::Handle
by Anonymous Monk on Sep 09, 2010 at 10:41 UTC
    ok, I got this working for the upload, however, the progress bar using the hook does not seem to be working, can you help me figure out why? here is the code below (I've been working on it for the past 11 hours so I'd really appreciate help if you can)
    ### Filename: file_upload_sys.data ## subs in file: #hook $q = new CGI (\&hook); # Set global value for session id (always members username) $sessid = $_un; # This is the file upload hook, where we can update our session # file with the dirty details of how the upload is going. sub hook { my ($filename,$buffer,$bytes_read,$file) = @_; # Calculate the (rough estimation) of the file size. This isn't # accurate because the CONTENT_LENGTH includes not only the file's # contents, but also the length of all the other form fields as we +ll, # so it's bound to be at least a few bytes larger than the file si +ze. # This obviously doesn't work out well if you want progress bars o +n # a per-file basis, if uploading many files. This proof-of-concept + only # supports a single file anyway. my $length = $ENV{'CONTENT_LENGTH'}; my $percent = 0; if ($length > 0) { # Don't divide by zero. $percent = sprintf("%.1f", (( $bytes_read / $length ) * 100) ); } # Write this data to the session file. open (SES, ">./$sessid.session"); print SES "$bytes_read:$length:$percent"; close (SES); } $action = $q->param("view"); if ($action eq "upl") { # Make a file upload hook. # They are first submitting the file. This code doesn't really + run much # until AFTER the file is completely uploaded. $filename = $q->param("incoming"); $handle = $q->upload("incoming"); $filename =~ s/(?:\\|\/)([^\\\/]+)$/$1/g; # Copy the file to its final location. open (FILE, ">/home/path/www/Videos/$filename") or die "Can't +create file: $!"; my $buffer; while (read($handle,$buffer,2048)) { print FILE $buffer; } close (FILE); # Delete the session file. unlink("./$sessid.session"); # Done. $_tfile = "/home/path/www/Videos/" . $filename; $type = uploadInfo($filename)->{'Content-Type'}; ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$c +time,$blksize,$blocks) = stat($_tfile); if(-e "/home/path/www/Videos/$filename") { $sess_ref->attr("show_message","success|I have successfull +y uploaded $filename to: http://www.domain.com/Videos/$filename<br /> +File type: $type<br />File size: $size bytes<br />"); window_redirect("$_url?page=$in{page}&do=$in{do}$sess_id") +; } else { $sess_ref->attr("show_message","error|I could not upload t +he file: $filename to: http://www.domain.com/Videos/$filename<br />Fi +le type: $type<br />File size: $size bytes<br />$_page_content"); window_redirect("$_url?page=$in{page}&do=$in{do}$sess_id") +; } } elsif ($action eq "ping") { # Now the meat of the CGI script. print "Content-Type: text/html\n\n"; # Checking up on the status of the upload. # Exists? if (-f "./$sessid.session") { # Read it. open (READ, "./$sessid.session"); my $data = <READ>; close (READ); print "$data"; } else { print "Error reading Upload progress..."; } if($sess_ref) { $sess_ref->close(); } if($dbh) { $dbh->disconnect(); } exit; } else { # Write this data to the session file. $_page_content .= qq~ <div id="webform"> <fieldset id="upload_form"> <legend>Approved Upload System</legend><br /> <br /> Hello, you have been verified as allowed to upload + files to the Videos directory for the the .net website...<br /> <br /> Please use the system below to upload files to thi +s directory.<br /> <br /> ~ . start_multipart_form(-name=>"uploadFile", -act +ion=>url(), -method=>"POST", -onSubmit=>"return startUpload();", id=> +"theform") . hidden(-name=>"page") . hidden(-name=>"do") . hidden(-name=>"view", -value=>"upl", -override=>1) + . $hidden_sess_id . filefield(-name=>'incoming', -class=>'formfield', -size=>50, -maxlength=>80) . qq~<br /> <br /> ~ . submit(-name=>"choice", -value=>"Upload The File", -cl +ass=>"submit") . qq~<br /> <br />~ . end_form() . qq~ </fieldset> <div id="progress-div" style="display: none; width +: 400px; margin: auto"> <fieldset> <legend>Upload Progress</legend><br /> <br /> <div id="trough"> <div id="bar" style="width: 0%"></div> </div> Received <span id="received">0</span>/<span id +="total">0</span> (<span id="percent">0</span>%) </fieldset><br /> <br /> </div> <br /> <br /> </div><br /> <br /> <div id="debug"></div>~ . q~ <script type="text/javascript"> // a jquery-like function, a shortcut to document.getElementBy +Id function $(o) { return document.getElementById(o); } // This function is called when submitting the form. function startUpload() { // Hide the form. document.getElementById('upload_form').style.display = "no +ne"; // Show the progress div. document.getElementById('progress-div').style.display = "b +lock"; // Begin making ajax requests. setTimeout("ping()", 1000); // Allow the form to continue submitting. return true; } // Make an ajax request to check up on the status of the uploa +d function ping() { var ajax = new XMLHttpRequest(); ajax.open("GET", "index.cgi?page=BackOffice&do=$in{do}&vie +w=ping&nw=2&rand=" + Math.floor(Math.random()*99999) + "$sess_id", tr +ue); ajax.onreadystatechange = function () { if (ajax.readyState == 4) { parse(ajax.responseText); } }; ajax.send(null); } // React to the returned value of our ping test function parse(txt) { document.getElementById("debug").innerHTML = "received fro +m server: " + txt; var parts = txt.split(":"); if (parts.length == 3) { document.getElementById("received").innerHTML = parts[ +0]; document.getElementById("total").innerHTML = parts[1]; document.getElementById("percent").innerHTML = parts[2 +]; document.getElementById("bar").style.width = parts[2] ++ "%"; } // Ping again! setTimeout("ping()", 1000); } </script>~; } 1;
    I get this error message: Error reading Upload progress... which appears to be that hook is not starting I think. I don't know how this program starts hook, but here is the raw program I found that does work:
    #!/usr/bin/perl -w use strict; use warnings; use CGI; use CGI::Carp "fatalsToBrowser"; # Make a file upload hook. my $q = new CGI (\&hook); # This is the file upload hook, where we can update our session # file with the dirty details of how the upload is going. sub hook { my ($filename,$buffer,$bytes_read,$file) = @_; # Get our sessid from the form submission. my ($sessid) = $ENV{QUERY_STRING}; $sessid =~ s/[^A-F0-9]//g; # Calculate the (rough estimation) of the file size. This isn't # accurate because the CONTENT_LENGTH includes not only the file's # contents, but also the length of all the other form fields as we +ll, # so it's bound to be at least a few bytes larger than the file si +ze. # This obviously doesn't work out well if you want progress bars o +n # a per-file basis, if uploading many files. This proof-of-concept + only # supports a single file anyway. my $length = $ENV{'CONTENT_LENGTH'}; my $percent = 0; if ($length > 0) { # Don't divide by zero. $percent = sprintf("%.1f", (( $bytes_read / $length ) * 100) ); } # Write this data to the session file. open (SES, ">$sessid.session"); print SES "$bytes_read:$length:$percent"; close (SES); } # Now the meat of the CGI script. print "Content-Type: text/html\n\n"; my $action = $q->param("do") || "unknown"; if ($action eq "upload") { # They are first submitting the file. This code doesn't really run + much # until AFTER the file is completely uploaded. my $filename = $q->param("incoming"); my $handle = $q->upload("incoming"); my $sessid = $q->param("sessid"); $sessid =~ s/[^A-F0-9]//g; $filename =~ s/(?:\\|\/)([^\\\/]+)$/$1/g; # Copy the file to its final location. open (FILE, ">./files/$filename") or die "Can't create file: $!"; my $buffer; while (read($handle,$buffer,2048)) { print FILE $buffer; } close (FILE); # Delete the session file. unlink("./$sessid.session"); # Done. print "Thank you for your file. <a href=\"files/$filename\">Here i +t is again.</a>"; } elsif ($action eq "ping") { # Checking up on the status of the upload. my $sessid = $q->param("sessid"); $sessid =~ s/[^A-F0-9]//g; # Exists? if (-f "./$sessid.session") { # Read it. open (READ, "./$sessid.session"); my $data = <READ>; close (READ); print $data; } else { print "0:0:0:error session $sessid doesn't exist"; } } else { print "0:0:0:error invalid action $action"; }
    that one always passes the session in the form, however, I just programmed a variable as a global variable to always be the member that is logged in's username so it is unique to member and always the same since this is just a upload system for a few admins.

    Any ideas?

    Thanks!
    Rich

      I'm no web programmer, nor do I play one on TV. Having said that, I'm guessing that your last paragraph contains the clue: Since HTTP is a stateless protocol, you can't rely on a global variable as you don't know *which* sessions upload is occurring. Keep in mind that multiple uploads may occur at one time. I suggest you use more of the original examples code and use the session to store your state.

      ...roboticus

      I get this error message: Error reading Upload progress... which appears to be that hook is not starting I think.

      I think it means the opposite: your hook is called but fails to read the session file. Your code looks like this...

      if (-f "./$sessid.session") { # Read it. open (READ, "./$sessid.session"); my $data = <READ>; close (READ); print "$data"; } else { print "Error reading Upload progress..."; }

      ... so your error message is telling you that "./$sessid.session" is not a plain file. So the file isn't there or you don't have permission to read it or $sessid has a garbage value.

      Also, you should check the return value of open and I would ditch the use of -f and just try to open the file instead...

      if ( open my $read_fh, '<', "./$sessid.session" ) { my $data = <$read_fh>; close $read_fh; print "$data"; } else { print "Error reading session file for session $sessid: $!"; }

      And finally, I agree with roboticus that you should stick with passing the session id in the form rather than using globals. What happens if one user starts a session and forgets about it and then opens another browser window and trys to upload something else? They wind up reusing the first session... havock ensues.

        No, the global session is based on their logged in username, since it is unique to the person logged in, then it will only be theirs... that is why I did that. it is not just a single name, it is their username.

        Since session is what it was using and they already have a session, I use Apache::MySql::Session, I went ahead and changed all of the code to that:
        #Was: (in sub hook): # Write this data to the session file. open (SES, ">./$sessid.session"); print SES "$bytes_read:$length:$percent"; close (SES); #Now (in sub hook): my $_ups = join(':',$bytes_read,$length,$percent); $sess_ref->attr("_uploadStatus_","$_ups"); #Was: (no sub, just in code [after upload complete]): # Delete the session file. unlink("./$sessid.session"); # Now in same place: # Delete the session value. $sess_ref->clear_attr("_uploadStatus_"); # last change: # Was if (-f "./$sessid.session") { # Read it. open (READ, "./$sessid.session"); my $data = <READ>; close (READ); print "$data"; } else { print "Error reading Upload progress..."; } #now: $data = $sess_ref->attr("_uploadStatus_"); if($data) { print "$data"; } else { print "1:1:1 No Session Value Found"; }
        Also of note, I see in hook, these values received in it:
        $filename,$buffer,$bytes_read,$file
        But I don't see a reference to hook passing those values to it. How does that work?

        Now 1:1:1 No Session Value Found prints... So I am thinking that hook is never even executed.

        What executes it?
        Thanks,
        Rich
        oops, I see what you said now, about the session and if they open a new browser window, did not read that line careful enough... I can fix that by not allowing another upload to start until the first is done.
        There will only be a few people that are allowed access to this, so I don't think they will need to use it to often. They are just uploading videos for product reviews, more then often they will be no more than 10MB, and he is on a super fast high speed connection so the upload should be fast enough for him to finish it before needing another.
        Using the code I changed it to, if I can get it to work, I can see if the _uploadStatus_ in the session exists if so, not build another form, and tell them to please wait until the other upload is completed, and then keep auto-refreshing that form with ajax until it is done so then it will rebuild the form so they can upload another.

        still need answered my other questions though, please, as I still cannot figure out hook, the values passed to it(or jus, received (why not passed, or where passed from?)) and what executes it.

        Rich