package Apache2::ASP::MediaManager; use strict; use base 'Apache2::ASP::UploadHandler'; use MIME::Types; use IO::File; my $mimetypes = MIME::Types->new(); #============================================================================== sub run { my ($s, $context) = @_; shift(@_); my $mode = $context->request->Form->{mode}; return unless ( ! $mode ) || ( $mode !~ m/^(create|edit)$/ ); my $filename = $s->compose_download_file_path( $context ); my $file = $s->compose_download_file_name( $context ); # Find its MIME type and set our 'ContentType' value: my $ext; unless( $mode ) { # Find its MIME type and set our 'ContentType' value: ($ext) = $file =~ m/.*?\.([^\.]+)$/; my $type = $ext ? $mimetypes->mimeTypeOf( $ext ) || 'application/octet-stream' : 'application/octet-stream'; $context->response->ContentType( $type ); }# end unless() # Call our extension hooks: if( $mode ) { if( $mode eq 'delete' ) { $s->before_delete( $context, $filename ) or return; $s->delete_file( $context, $filename ); return $s->after_delete( $context, $filename ); } elsif( defined(my $handler = $s->modes( $mode )) ) { return $handler->( $s, $context ); }# end if() }# end if() # Get the readable filehandle: unless( -f $filename ) { $context->response->Status( 404 ); return; }# end unless() # Call our before- hook: $s->before_download( $context, $filename ) or return; # Wait until "before_download" has cleared before we open a filehandle: my $ifh = $s->open_file_for_reading( $context, $filename ); # Send any HTTP headers: $s->send_http_headers($context, $filename, $file, $ext); # Print the file out: if( (stat($ifh))[7] < 1024 ** 2 ) { # File is < 1M, so just slurp and print: local $/; $context->response->Write( scalar(<$ifh>) ); } else { while( my $line = <$ifh> ) { $context->response->Write( $line ); }# end while() }# end while() $context->response->Flush; # Done! $ifh->close; # Call our after- hook: $s->after_download( $context, $filename ); }# end run() #============================================================================== sub send_http_headers { my ($s, $context, $filename, $file, $ext) = @_; # Send the 'content-length' header: $context->r->err_headers_out->{'Content-Length'} = (stat($filename))[7]; # PDF files should force the "Save file as..." dialog: my $disposition = (lc($ext) eq 'pdf') ? 'attachment' : 'inline'; $file =~ s/\s/_/g; $context->r->err_headers_out->{'content-disposition'} = "$disposition;filename=" . $file . ';yay=yay'; }# end send_http_headers() #============================================================================== sub delete_file { my ($s, $context, $filename) = @_; die "'$filename' is a directory, not a file" if -d $filename; return unless -f $filename; unlink( $filename ) or die "Cannot delete file '$filename' from disk: $!"; }# end delete_file() #============================================================================== sub open_file_for_writing { my ($s, $context, $filename) = @_; # Try to open the file for writing: my $ofh = IO::File->new(); $ofh->open($filename, '>' ) or die "Cannot open file '$filename' for writing: $!"; $ofh->binmode; $ofh->autoflush(1); return $ofh; }# end open_file_for_writing() #============================================================================== sub open_file_for_reading { my ($s, $context, $filename) = @_; # Try to open the file for reading: my $ifh = IO::File->new(); $ifh->open($filename, '<' ) or die "Cannot open file '$filename' for reading: $!"; $ifh->binmode; return $ifh; }# end open_file_for_reading() #============================================================================== sub open_file_for_appending { my ($s, $context, $filename) = @_; # Try to open the file for appending: my $ofh = IO::File->new(); $ofh->open($filename, '>>' ) or die "Cannot open file '$filename' for appending: $!"; $ofh->binmode; $ofh->autoflush(1); return $ofh; }# end open_file_for_appending() #============================================================================== sub compose_download_file_path { my ($s, $context) = @_; # Compose the local filename: my $file = $context->request->Form->{file}; my $filename = $context->config->web->media_manager_upload_root . '/' . $file; return $filename; }# end compose_file_path() #============================================================================== sub compose_download_file_name { my ($s, $context) = @_; # Compose the local filename: my $file = $context->request->Form->{file}; return $file; }# end compose_file_name() #============================================================================== sub compose_upload_file_name { my ($s, $context, $Upload) = @_; # my $filename = $Upload; my ($filename) = $Upload->{upload}->{upload_filename} =~ m/.*[\\\/]([^\/\\]+)$/; if( ! $filename ) { $filename = $Upload->{upload}->{upload_filename}; }# end if() return $filename; }# end compose_upload_file_name() #============================================================================== sub compose_upload_file_path { my ($s, $context, $Upload, $filename) = @_; unless( defined($filename) && length($filename) ) { die "\$filename not provided"; }# end unless() return $context->config->web->media_manager_upload_root . "/$filename"; }# end compose_upload_file_path() #============================================================================== sub upload_start { my ($s, $context, $Upload) = @_; shift(@_); $s->SUPER::upload_start( $context, $Upload ); my $filename = $s->compose_upload_file_name( @_ ); # Make sure we can open the file for writing: my $target_file = $s->compose_upload_file_path( $context, $Upload, $filename); # Open the file for writing: my $ofh = $s->open_file_for_writing($context, $target_file); print $ofh delete($Upload->{data}); # Done with the filehandle: $ofh->close; # Store some information for later: $ENV{filename} ||= $target_file; $ENV{download_file} ||= $filename; # Depending on the 'mode' parameter, we do different things: local $_ = $s->_args('mode'); if( /^create$/ ) { $s->before_create($context, $Upload); } elsif( /^edit$/ ) { $s->before_update($context, $Upload); } else { die "Unknown mode: '$_'"; }# end if() }# end upload_start() #============================================================================== sub upload_hook { my ($s, $context, $Upload) = @_; shift(@_); $s->SUPER::upload_hook( @_ ); my $filename = eval { my $name = $ENV{filename}; # $context->r->pnotes( 'filename' ); $name; } or return; my $ofh = $s->open_file_for_appending($context, $filename); no warnings 'uninitialized'; print $ofh delete($Upload->{data}); $ofh->close; }# end upload_hook() #============================================================================== sub upload_end { my ($s, $context, $Upload) = @_; shift(@_); $s->SUPER::upload_end( @_ ); # Return information about what we just did: my $info = { new_file => $ENV{filename}, filename_only => $ENV{download_file}, link_to_file => "/media/" . $ENV{download_file}, }; $Upload->{$_} = $info->{$_} foreach keys(%$info); # Depending on the 'mode' parameter, we do different things: # local $_ = $context->request->Form->{mode}; my $form = $context->request->Form; $form = $context->request->Form; if( $form->{mode} =~ /^create$/ ) { $s->after_create($context, $Upload); } elsif( $form->{mode} =~ /^edit$/ ) { $s->after_update($context, $Upload); } else { die "Unknown mode: '$_'"; }# end if() }# end upload_end() #============================================================================== sub before_download { my ($s, $context) = @_; }# end before_download() #============================================================================== sub after_download { my ($s, $context) = @_; }# end after_download() #============================================================================== sub before_create { my ($s, $context, $Upload) = @_; 1; }# end before_create() #============================================================================== sub before_update { my ($s, $context, $Upload) = @_; 1; }# end before_update() #============================================================================== sub after_create { my ($s, $context, $Upload) = @_; }# end after_create() #============================================================================== sub after_update { my ($s, $context, $Upload) = @_; }# end after_update() #============================================================================== sub before_delete { my ($s, $context, $filename) = @_; }# end before_delete() #============================================================================== sub after_delete { my ($s, $context, $filename) = @_; }# end after_delete() 1;# return true: =pod =head1 NAME Apache2::ASP::MediaManager - Instant file management for Apache2::ASP applications =head1 SYNOPSIS Create a file in your C folder named C and add the following: package MM; use strict; use warnings 'all'; use base 'Apache2::ASP::MediaManager'; use vars __PACKAGE__->VARS; sub before_download { my ($s, $context, $filename) = @_; warn "About to download '$filename'"; return 1; } sub after_download { my ($s, $context, $filename) = @_; warn "Finished download of '$filename'"; return 1; } sub before_create { my ($s, $context, $Upload) = @_; warn "About to create '$Upload->{filename_only}'"; return 1; } sub after_create { my ($s, $context, $Upload) = @_; warn "Just created '$Upload->{filename_only}'"; return $Response->Redirect( $ENV{HTTP_REFERER} ); } sub before_update { my ($s, $context, $Upload) = @_; warn "About to update '$Upload->{filename_only}'"; return 1; } sub after_update { my ($s, $context, $Upload) = @_; warn "Just updated '$Upload->{filename_only}'"; return $Response->Redirect( $ENV{HTTP_REFERER} ); } sub after_delete { my ($s, $context, $filename) = @_; warn "Just deleted '$filename'"; return $Response->Redirect( $ENV{HTTP_REFERER} ); } 1;# return true: Then create a file in your C folder named C:

File Upload Test

Existing Files (if any)

<% opendir my $dir, $Config->web->media_manager_upload_root; while( my $file = readdir($dir) ) { next unless -f $Config->web->media_manager_upload_root . '/' . $file; %> <%= $file %>
<% }# end while() %> B: Notice the C in the C attribute of the C

tag. The C tells the MediaManager that we are B a file, and the C will allow us to track the progress of the upload. B: Check your configuration, where you see: ... ... @ServerRoot@/MEDIA ... ... Make B<*sure*> that your webserver has ownership or read/write access to that folder and all of its contents. When you access http://yoursite.com/mm-test.asp in your browser and submit the form, C will save it under your C folder for you. =head1 DESCRIPTION Handling file uploads can be a real pain. Restricting file uploads and downloads to a select group of users is also problematic. B And then there was C. Now you can have fully-functional file uploads in seconds. =head1 UPLOAD PROGRESS INDICATORS C makes it easy to provide upload progress indicators. Remember that C parameter in the C attribute of your form? While the upload is taking place, the C<$Session> object is getting updated with the status of the upload. If you were to make another handler - C - and insert the following code: package UploadProgress; use strict; use base 'Apache2::ASP::FormHandler'; use vars __PACKAGE__->VARS; sub run { my ($s, $context) = @_; my $uploadID = $Form->{uploadID}; $Session->{"upload$uploadID" . "percent_complete"} ||= 0; $Response->Expires( -30 ); $Response->Write( $Session->{"upload$uploadID" . "percent_complete"} ); }# end run() 1;# return true: And add call out to it via AJAX - you can get real-time upload progress information about the current upload. Example: window.setInterval(function() { httpOb.open("GET", "/handlers/UploadProgress?uploadID=2kj4hkj234h", true); httpOb.onreadystatechange = function() { if( httpOb.readyState == 4 ) { document.getElementById("percent_complete").innerHTML = httpOb.responseText + '%'; }// end if() }; }, 1000); You should also add an element with an id of "percent_complete" to he form:
0%
=head1 PUBLIC PROPERTIES None. =head1 PUBLIC METHODS Nothing you need to worry about. =head1 EVENTS B argument>: The C<$Upload> argument is an instance of L. =head2 before_download( $self, $context ) Called before allowing a file to be downloaded from the server. B: This method must return true, or the file will not be downloaded. =head2 after_download( $self, $context ) Called after allowing a file to be downloaded from the server. =head2 before_create( $self, $context, $Upload ) Called before allowing a new file to be uploaded to the server. B: This method must return true, or the file will not be created. =head2 after_create( $self, $context, $Upload ) Called after allowing a new file to be uploaded to the server. =head2 before_update( $self, $context, $Upload ) Called before allowing a new file to be uploaded to replace an existing file. B: This method must return true, or the file will not be updated. =head2 after_update( $self, $context, $Upload ) Called after a new file has been uploaded to replace an existing file. =head2 before_delete( $self, $context, $filename ) Called before deleting a file. B: This method must return true, or the file will not be deleted. =head2 after_delete( $self, $context, $filename ) Called after deleting a file. =head1 BUGS It's possible that some bugs have found their way into this release. Use RT L to submit bug reports. =head1 HOMEPAGE Please visit the Apache2::ASP homepage at L to see examples of Apache2::ASP in action. =head1 AUTHOR John Drago L =head1 COPYRIGHT AND LICENSE Copyright 2007 John Drago, All rights reserved. This software is free software. It may be used and distributed under the same terms as Perl itself. =cut