RiverViews Header
River Views

Sat, 03 Sep 2011
The Best Web App Framework

Catalyst is the best web app framework.

I feel like there is lots more to say, but it’s all been said before.

I’ve been developing webapps for the past decade. I’ve observed the trends; I’ve tried different “best new solutions”. I wrote my own web and DBI framework; I eventually abandoned it. Web development was looking grim; I found Catalyst.

And best of all, I could keep using Perl. I couldn’t ask for anything more. (Well, maybe an ORM, but that’s another story, with another happy ending.)

/perl | permanent link
Sun, 31 Jan 2010
use DBIx::Class::InflateColumn::FS instead

DBIx::Class::InflateColumn::File - DEPRECATED

It is time to stop using DBIx::Class::InflateColumn::File. This article gives some tips and examples for switching to DBIx::Class::InflateColumn::FS.

From DBIx::Class::InflateColumn::File:

DBIx::Class::InflateColumn::File - DEPRECATED (superseded by DBIx::Class::InflateColumn::FS)

Deprecation Notice

This component has a number of architectural deficiencies and is not recommended for further use. It will be retained for backwards compatibility, but no new functionality patches will be accepted. Please consider using the much more mature and actively supported DBIx::Class::InflateColumn::FS

I switched to DBIx::Class::InflateColumn::FS because I had problems with DBIx::Class::InflateColumn::File not deleting all files from the file system; and that was due to one of the “number of architectural deficiencies”. I am happy to see further development for file-based storage will be focussed on one package.

So let’s look at ways of using DBIx::Class::InflateColumn::FS instead

If you have been using DBIC::IC::File and want to migrate to DBIC::IC::FS, there are some changes to assigning column values.

The examples below assume IC::FS is being used in a Catalyst environment. We are also assuming the desired filename is already known (part of the $rec object) or we can use a random (UUID) filename.

Using IC::File required assigning a hashref to the file column, eg:

$rec->media_orig_file({ 
    handle   => $c->req->upload('myupload')->fh, 
    filename => $c->req->upload('myupload')->basename 
});

While IC::FS just wants a filehandle:

$rec->media_orig_file( $c->req->upload('myupload')->fh );

IC::FS also has a different way of naming the file on disk and specifying the directory to write it to.

The is_fs_column column type from IS::FS offers a couple of methods to control its behavior:

fs_file_name

Provides the file naming algorithm. Override this method to change it. This method is called with two parameters: The name of the column and the column_info object.

_fs_column_dirs

Returns the sub-directory components for a given file name. Override it to provide a deeper directory tree or change the algorithm.

The following goes in MyApp::Schema::Result::Media.pm; it assumes I already assigned values to user, name & mime_type. It also allows me to use a different filename depending on which column is being set.

sub fs_file_name {
    my ($self, $column, $column_info) = @_;
    my MIME::Type $img_mimetype = MIME::Types->new->type($self->mime_type); #'image/jpeg'
    my $size = $column eq 'media_thumb_file' ?
                    'thumb' :
               $column eq 'media_orig_file'  ?
                    'orig' :
                    '';
    return sprintf("%05d-%s_%s.%s",
        $self->user->id,
        $self->name,
        $size,
        $img_mimetype->subType)
}

Or if you want random file names but need to control the file extension, use a method similar to IC::FS->fs_file_name; it assumes I have assigned a value to mime_type.

sub fs_file_name {
    my ($self, $column, $column_info) = @_;
    my MIME::Type $img_mimetype = MIME::Types->new->type($self->mime_type); #'image/jpeg'
    return sprintf("%s.%s", DBIx::Class::UUIDColumns->get_uuid, $img_mimetype->subType)
}

Of course if you just want a random filename, no need to override the fs_file_name method at all. And if you need to use the filename from the upload param, but don’t want to create a column in your table for the name, then use (eg) a Moose attribute for it in your Result class. And set the filename attribute before setting the file column value.

has 'upload_filename' => ( is => 'rw' );

sub fs_file_name {
    my ($self, $column, $column_info) = @_;
    my MIME::Type $img_mimetype = MIME::Types->new->type($self->mime_type); #'image/jpeg'
    return sprintf("%s.%s", $self->upload_filename, $img_mimetype->subType)
}

and elsewhere…

$rec->upload_filename( $c->req->upload('myupload')->basename );
$rec->media_orig_file( $c->req->upload('myupload')->fh );

Note, I haven’t tried that method, but it should work fine. MyApp just uses a random (UUID) filename.

What directory will files be stored in?

There are two parts to configuring the directory where files will be written.

The first is defining which directory will be used for all files written for a given column. I’m using class method path_to that I setup for uses like this. There are plenty of other ways to specify the directory value to fs_column_path.

__PACKAGE__->add_columns(

  "media_thumb_file",
  {
    data_type       => "varchar",  size => 255,
    is_fs_column    => 1,
    fs_column_path  => __PACKAGE__->path_to('root','static','media'),
  },
  "media_orig_file",
  {
    data_type       => "varchar",  size => 255,
    is_fs_column    => 1,
    fs_column_path  => __PACKAGE__->path_to('root','data','media'),
  },

};

And second you specify which sub-directory will be used for the file. You can use the default for IC::FS, from the POD:

Within the path specified by fs_column_path, files are stored in sub-directories based on the first 2 characters of the unique file names. Up to 256 sub-directories will be created, as needed. Override _fs_column_dirs in a derived class to change this behavior.

Or use a variation of the default method; this uses two-level sub-directory structure:

sub _fs_column_dirs {
    shift;
    my $filename = shift;

    $filename =~ s/(..)(..).*/$1\/$2/;
    return $filename;
}

Or let’s use a value from $rec; mimics IC::File behavior:

sub _fs_column_dirs {
    return shift->id;
}

At some point you will need to refer to the file again, and one common use is specifying a URL which points to the file as a resource on your server. IC::FS doesn’t give us a method we can use for that, but it’s very simple to create one in our Result class.

sub fs_file_path {
    my ($self, $column) = @_;
    my $fh = $self->$column;
    return $fh->relative( $self->result_source->column_info($column)->{fs_column_path} )->stringify;
}

The fs_file_path method uses column_info to get the base directory for the file (as set in fs_column_path), then it uses $fh->relative method to get the end of the directory/file path. And that value can used for creating src attributes, eg. from a TT template:

[%  media = rec
    img_full_src   = c.uri_for("/static/media/", media.fs_file_path('media_full_file' ));
    img_thumb_src  = c.uri_for("/static/media/", media.fs_file_path('media_thumb_file'));
%]
<a href="[% img_full_src %]" title="[% media.name %]"
><img src="[% img_thumb_src %]"  width="[% media.width %]"  height="[% media.height %]" /></a>

First we assigned $rec to a local variable media. We call fs_file_path twice for two different image columns. One value we use for setting src to the thumbnail image, and the second is for setting the href in the anchor tag. (Note, you will need the latest version of Catalyst to avoid a path encoding bug with uri_for, at least when using the above syntax.)

More Advanced Usage

One of the more common reasons to use a filesystem-backed storage is for image and other media files. And when there is one media file there is often another since we’ll need to offer different sizes and maybe even different formats. So let’s get our Result::Media class to assist with making multiple formats simple to manage.

In one of your controller actions:

my $image = $c->req->upload('media_image');
my $name = $image->filename || 'image'; ## || $image->basename
$name =~ s/(.*)\.(.*?)$/$1/; ## remove file extension

my $media = $c->model('DBIC::Media')->create({ name => $name });
$media->set_image($image->fh);
$media->update;

And in Result::Media, create a method which stores both the original uploaded image and a thumbnail copy. We use Imager to handle creating the thumbnail image.

sub set_image {
    my $self = shift;
    my $img_fh = shift;

    Imager->set_file_limits(bytes => 10_000_000);   # limit to 10 million bytes of memory usage
    Imager->set_file_limits(width => 1024, height => 1024); # limit to 1024 x 1024

    $img_fh->seek(0,0);
    binmode $img_fh;

    my $img_orig = Imager->new;
    if ($img_orig->read(fh=>$img_fh)) {

        my $img_format = $img_orig->tags(name=>'i_format');
        my MIME::Type $img_mimetype  = MIME::Types->new->mimeTypeOf($img_format);

        ## set some other columns we use for metadata
        $self->mime_type("$img_mimetype"); # $img_mimetype needs to be stringified
        $self->size_width($img_orig->getwidth); 
        $self->size_height($img_orig->getheight);

        $img_fh->seek(0,0);
        $self->media_orig_file( $img_fh );

        ## Let's grab copy as thumbnail image, scaling and cropping along the way
        my $img_thumb = $img_orig->scale(
            xpixels   => 75,
            ypixels   => 100,
            type      => 'max',
            ## preview, mixing, normal - preview is faster than mixing which is much faster than normal
            qtype     => 'mixing',
        )->crop(width=>75, height=>100) or 
                $self->throw_exception("Could not scale/crop thumbnail image: " . $img_orig->errstr);


        my $img_thumb_fh = IO::File->new_tmpfile;
        $img_thumb->write(fh => $img_thumb_fh, type => $img_format) or 
                $self->throw_exception("Could not write file handle for thumbnail image: " . $img_thumb->errstr);

        $img_thumb_fh->seek(0,0);
        $self->media_thumb_file( $img_thumb_fh );

    } else {
        $self->throw_exception("Could not read image from file handle: " . $img_orig->errstr);
    }
}

So with just a few lines in our controller, we can add media files of different sizes and formats from one file upload.

Conclusions

Using DBIx::Class::InflateColumn::FS is easy, and there is enough flexibility to allow us to store files in way that meets individual file system requirements.

Through the use of _fs_column_dirs we have control of scalability issues from directory size limitations, and along with fs_file_name we have control over the filename so we can assign user-meaningful values or something more arbitrary like UUID values.

We can easily get the path for a file to use in a src attribute using a method like fs_file_path defined in our Result class.

There are other usage scenarios that may require different solutions. Eg. (as discussed in the mailing list) how does HTML::FormFu handle file uploads when used with HTML::FormFu::Model::DBIC? I would like to see seamless integration with HTML::FF, I don’t know what’s required to make that happen though.

Final Words… DBIx::Class::InflateColumn::FS is another great example of why the DBIx::Class and Catalyst projects are such a joy to work with.

/perl | permanent link
Thu, 17 Dec 2009
How much do you know about the proposed internet filter?

You have probably heard about the mandatory internet filter by now, and as someone close to the issue I thought I should share some facts about the proposal. There is so much fear-mongering around the issue that it’s hard for the facts to surface.

First, for those of you who are not aware, Senator Conroy has been proposing a mandatory internet filter for all Australians; homes, businesses, everywhere. I don’t really have an issue with the concept of a filter; we already have plenty of censorship which is helpful for society (eg. movie/tv ratings, games classifications, etc). But that is not what the filter will be doing.

The filter is being presented under the banner of “protecting the children”, but not only does it fall way short of that goal; it has much potential to actually make child abuse worse.

There are lots of IT people talking about how it will slow down the Internet for all of Australia. The government is white-washing that fact, but that’s no big deal; anyone who cares to read about it can see that our internet will be slower if the filter is implemented. The part which is not very clear is how it has the potential to increase child abuse.

The following may to be too confronting for some of you, but I believe it’s important to define what child abuse is in the context of “protecting the children”. There are two distinct issues at play; kids who view inappropriate content (net nasties), and kids who are abused and become victims.

I’m sure if you stop to think about it; the second problem is much more serious. But the filter does nothing (& I mean nothing) to stop children from being abused. In fact, it has opposite effect. One of the things the government is not readily sharing is that Australian Federal Police have a unit that works internationally to combat child abuse, and their funding has been cut in order to help fund the new filter.

Did you get that part? We already have cops “on the ground” who are combating child abusers. The government is taking money away from them so that they can put the mandatory filter in place. So next you ask, but what about the pedophiles, won’t they be blocked from all the nasty content. And NO, they won’t.

That’s when we start getting into the technical details, and trust me that it would take a determined person about 10 minutes to circumvent the filter. A high school student demonstrated that with the trial filter; he had by-passed it in less than 10 minutes. And high school kids are notorious for sharing things like how to get around all the limitations placed in front of them.

If any of you are interested I can share the technical facts which make the filter a complete joke, but that will be too boring for most of you.

Next we can look at other countries like China and N Korea who have been condemned for their mandatory filters. We will be joining their ranks. Note I said mandatory filter. We have other options.

Having filters is a good thing, but it’s not the government’s place to enforce them, especially when they will be so ineffective (& possibly harmful) against the actual issue of “protecting the children”. And we already have other solutions today to protect the children.

Most people think of “protecting the children” as shielding them from net nasties. There are plenty of ways to do that already. The government even has a site already where parents can download filters and install them at home. Schools (& parents) can sign-up with an ISP which provides a “clean feed”. There is a long list of solutions which don’t take funds away from the AFP (who are already combatting child abuse) and don’t slow down the internet for every single person in this country.

One of my colleagues summarized it quite well in his blog:
Conroy’s Clean Feed Won’t Block Child P*rn

While Senator Conroy has failed to explain in coherent terms why the Government wants the clean feed, there are plenty of reasons why it’s a terrible idea:

  • There’s no real problem to solve online.
  • Even if there was, I can’t see any serious public demand to solve it.
  • Even if there was public demand to solve the problem, Conroy’s report, incomplete as it is, indicates that a clean feed won’t do the job.
  • Even if the clean feed was a workable solution to a problem that doesn’t seem to exist, it will be both expensive and unreliable.
  • Even if the clean feed was a magically technically perfect system, it will be administered by public servants who have trouble spelling HTTP - let alone controlling the content it carries.
  • And finally, even if a functional net filter was scrupulously administered by experts, it’s highly likely the blacklist will leak again.

All of which gets us to the crux of the matter: the Government’s own report demonstrates that easily circumvented censorware is ineffective, and their own experience shows that they’re, well, completely hopeless at maintaining blacklist confidentiality.

Even Google has stepped into the fray condemning the mandatory filter.
Google weighs in to Aussie firewall row

And they are not the only ones. “Save the Children” has urged the Federal Government to abandon its plans to censor the internet, saying it will not be effective in protecting kids from online dangers.
Save the Children opposes internet filter

I don’t really have suggestions for what any of you can do about it. And some of you may even be in favour of the mandatory filter so are happy to see it going ahead. But I strongly feel that more Australians should be aware of the true implications of the mandatory filter coming from behind the banner of “protect the children”.

Whether you agree with me or not, I do hope you take a moment to actually think about the issue rather than just going along with Conroy’s statements. And if you have any questions, I’ll be happy to answer them if I can, or at least point you to some online references.

Some other references:

permanent link
Fri, 30 May 2008
Living descendants of Shipka Ebenezer

Sally Cutter announced today that she has been in touch with someone she has confirmed to be a direct descendant of Shipka Ebenezer. Sally also said there is an Ebenezer family secret, that has been closely guarded for generations.

Sally refused to share contact details, but I was able to steal her log files and see who she contacted. I found video calls made to one contact 3 times in a 65 minute period. I also found this address zai.press@gmail.com.

/treasurehunt | permanent link
Thu, 29 May 2008
Living descendants of Shipka Ebenezer

With all the excitement recently following the discovery of evidence that Ebenezer’s Vault does exist, a few genealogists wanted to know whether descendants could be traced through today. Sally Cutter claims to have found a direct living descendant of Shipka Ebenezer.

Sally has been unable to make contact and will not make the person’s name public until she has confirmed whether direct descendant line is accurate. In the meantime she has been debating with her colleagues whether any important family history or heirlooms still exist today.

The common belief is that all of Ebenezer’s treasure was locked away in his vault, so it’s unlikely that any family heirlooms will be passed down from Shipka. But letters between family members about “Pa’s secret crate” have been found in the past few decades. Sally says there is a good chance the family secret could still exist today, and she is going to find out.

I’ll update this page as more info becomes available.

/treasurehunt | permanent link

River Views
Views and comments from the Colo River in Australia.

Charlie Garrison
charlie@riverviews.com.au

Subscribe
Subscribe to a syndicated (RSS) feed of River Views.

blosxom logo