Perl File Management


We needed a simple file upload, view and delete app for one of our projects. Since my company uses a lot of perl, I was tasked to write the web app in the language. I have had some experience working on Perl before for other in-house projects, but this was the first time I was working from scratch.

We decided the first prototype should not have account management because the project was still in its very early stages and should only have the bare essential UI to aid in file management on our servers. I used Mojolicious, Perl’s Web framework and got to work.

The first step was to do directory management on the server. I used the following directory structure.

  • public
    • css
    • images
    • js
    • ul-projects
      • project 1
      • project 2
  • templates
    • layouts
  • main.pl

I was using morbo as my development server. It comes packaged with mojolicious and auto-compiles after each source code change so I didn’t have to do it manually.

Mojolicious uses extremely simple route handling which allows for very short code where a lot of code would otherwise be needed. A simple get can be routed as simply as:

get '/' => sub {
  my $c = shift;
  $c->render(template => 'magic');
};

This routes the root (‘/’) to the anonymous subroutine which is executed. Here, the root renders the embedded perl template and serves to the user.

Embedded Perl is a bit like php, where code is written inside the html file and is executed at rendering. An embedded perl file to generate an html list would look something like this:

<%
	my $projects = stash 'projects'; #variables passed in stash (a variable hash)
	my @projects = @$projects; #the @$ dereferences the array reference $projects. 
	my $length = @projects -1; #accessing an array in a scalar context gives the last index
	for my $i (0 .. $length)
	{ %>
	<div class="checkbox">
		<label>
			<input type="checkbox" name="<%= $projects[$i] %>"><%= $projects[$i] %>
		</label>
	</div>
	<%}
	%>

Notice how normal htmls interlaced along with the perl. The dereferencing the array reference on line 2 was one of the most annoying things which took me a while to figure out. The stash is a variable hash which is passed in to the template when rendering. This looks something like this:

get '/manage' => sub {
  my $c = shift; # gets self, something like 'this' in other languages
  my $root = "public/ul-projects";

  my $projects = read_dir($root);
  $c->stash('projects', $projects); # here I am stashing the array reference to the projects into self which is then passed in to the template.

  $c->render(template => 'manage');
};

The code above is also the controller for viewing the files in the ul-projects directory. The manage controller gets the list of the directories and then passes into the stash, whence it is passed into the template when the html is being rendered.

Going back to the project, I decided to make the file uploader first. I used a post controller to post files to the server along with the other necessary data to allow file management.

post '/video-upload' => sub {
  my $self = shift;

  # Uploaded image(Mojo::Upload object)
  my $file = $self->req->upload('file');
  my $name = $self->req->content->parts->[0]->asset->slurp;
...}

This short code above takes the $self object and goes through the upload request where it takes the ‘file’ object. The second line goes to the name of the project in the request which is also being sent with the same post.

After validation checks on the file object such as checking for content type and or file size, a project directory is created with the name of the project being sent in. I am using the File::Path module to make and delete directory structures on the file system.

my $file_type = $file->headers->content_type;
  my %valid_types = map {$_ => 1} qw(video/mp4 video/avi);

unless ($valid_types{$file_type}) { #checks for content type
	return $self->render(
	  template => 'error',
	  message  => "Upload fail. Content type is wrong."
	);
}
my $exts = {'video/mp4' => 'mp4', 'video/avi' => 'avi'};
my $ext = $exts->{$file_type};

my $THIS_PROJECT_BASE = "/$name";
my $THIS_PROJECT_DIR  = app->home->rel_file('/public/ul-projects') . $THIS_PROJECT_BASE;

unless (-d $THIS_PROJECT_DIR) {
mkpath $THIS_PROJECT_DIR or die "Cannot create dirctory: $THIS_PROJECT_DIR";
}

# Image file
my $video_file = "$THIS_PROJECT_DIR/" . create_filename($name). ".$ext";

# If file is exists, Retry creating filename
while(-f $video_file){
$video_file = "$THIS_PROJECT_DIR/" . create_filename($name) . ".$ext";
}

# Save to file
$file->move_to($video_file);

This was all for the controller. Going to the UI, I used dropzoneJS, a very simple javascript library to handle uploads. The library allows a lot of validation checks like size, type, number of files with very simple APIs. Bootstrap was used for various UI elements itself.

Perl CMS 1

The home page for uploads.

Perl CMS 1

The page for viewing directories.

The controller for deleting the file is as follows:

get '/deleteProj' => sub {
  my $self = shift;
  my $PROJECT = $self->req->url->query->param('name');

  my $PROJECT_DIR  = app->home->rel_file('/public/ul-projects') . "/$PROJECT";

  unless ($PROJECT eq "") {
    remove_tree($PROJECT_DIR) or die "Cannot delete dirctory: $PROJECT_DIR";
  }

  $self->render(text => "Deleted $PROJECT");
};

So this is how we got a simple file management app using perl. It was simple to make and a good experience in learning the basics of the language.

Till next time.

-Taha


comments powered by Disqus