--- /dev/null
+package Newslash::Plugin::JavaScriptLoader;
+use Mojo::Base 'Mojolicious::Plugin';
+
+use File::Spec;
+use File::Basename qw(fileparse);
+use Digest::MD5 qw(md5_hex);
+use IPC::Open3;
+use Symbol qw(gensym);
+use IO::Select;
+use POSIX ":sys_wait_h";
+use Mojo::Util qw(dumper);
+use Encode;
+
+use constant KVS_PREFIX => "JSL:";
+use constant DEFAULT_MEM_EXPIRE => 60;
+use constant DEFAULT_KVS_EXPIRE => 60;
+
+sub _kvs {
+ return shift->{app}->kvs;
+}
+
+sub _cache {
+ return shift->{app}->cache;
+}
+
+sub set_cache {
+ my ($self, $key, $value) = @_;
+ my $mem_expire = $self->{conf}->{mem_expire};
+ my $kvs_expire = $self->{conf}->{kvs_expire};
+ $mem_expire = DEFAULT_MEM_EXPIRE if !defined $mem_expire;
+ $kvs_expire = DEFAULT_KVS_EXPIRE if !defined $kvs_expire;
+
+ $self->_kvs->set(KVS_PREFIX . $key, $value, $kvs_expire);
+ $self->_cache->set(KVS_PREFIX . $key, $value, $mem_expire);
+}
+
+sub get_cache {
+ my ($self, $key) = @_;
+ my $expire = $self->{conf}->{mem_expire};
+ $expire = DEFAULT_MEM_EXPIRE if !defined $expire;
+
+ my $value = $self->_cache->get(KVS_PREFIX . $key);
+ return $value if defined $value;
+
+ $value = $self->_kvs->get(KVS_PREFIX . $key);
+ if (defined $value) {
+ $self->_cache->set(KVS_PREFIX . $key, $value, $expire);
+ return $value;
+ }
+
+ # no cache found
+ return;
+}
+
+sub get_content {
+ my ($self, $basename) = @_;
+ my $content = $self->get_cache($basename);
+ return $content if defined $content;
+
+ $content = $self->_load_js($basename);
+ if (defined $content) {
+ $self->set_cache($basename, $content);
+ $self->set_cache($content->{path}, $content);
+ return $content
+ }
+ return;
+}
+
+sub cached_basedir {
+ return "/js_cache";
+}
+
+sub _load_js {
+ my ($self, $basename) = @_;
+
+ # check if file exists
+ my $abs_path = $self->_basename_to_absolute_path($basename);
+ return if (!-f $abs_path || !-r $abs_path);
+
+ # load file
+ open( my $fh, $abs_path ) or return;
+ my $js_body = do { local($/); <$fh> } ;
+ close($fh);
+
+ my $md5;
+ if (utf8::is_utf8($js_body)) {
+ $md5 = md5_hex(Encode::encode('utf8', $js_body));
+ }
+ else {
+ $md5 = md5_hex($js_body);
+ }
+ my $path;
+ if ($self->{mode} eq "development") {
+ my $basedir = $self->{conf}->{source_directory};
+ $basedir =~ s/^public//g;
+ $path = "$basedir/$basename";
+ }
+ else {
+ my ($base, $dir, $ext) = fileparse($basename, qw/\.[^.]*/);
+ $dir = "" if $dir eq "./";
+ my $basedir = $self->cached_basedir;
+ $path = "$basedir/$dir${base}.$md5$ext";
+ }
+
+ my $result = { md5 => $md5,
+ content => $js_body,
+ path => $path,
+ type => "text/javascript",
+ };
+ return $result;
+}
+
+sub _basename_to_absolute_path {
+ my ($self, $basename) = @_;
+ my $base_dir = $self->{conf}->{base_directory};
+ if (!$base_dir) {
+ $base_dir = $self->{app}->home;
+ }
+ if (!$base_dir) { # fallback
+ $base_dir = "./";
+ }
+ my $src_dir = $self->{conf}->{source_directory};
+ my $path = File::Spec->catdir($base_dir, $src_dir, $basename);
+ $path = File::Spec->rel2abs($path);
+ return $path;
+}
+
+sub _split_md5 {
+ my ($self, $path) = @_;
+ if ($path =~ m/^(.+)\.([0-9a-f]+)(\.\w+)$/) {
+ return ($1 . $3, $2);
+ }
+ return ($path, "");
+}
+
+sub register {
+ my ($self, $app, $conf) = @_;
+
+ $self->{conf} = {%$conf, %{$app->config->{JavaScriptLoader}}};
+ $self->{app} = $app;
+ $self->{mode} = $self->{conf}->{mode} || $app->mode;
+
+ if (!$self->{conf}->{source_directory}) {
+ $app->log->warn("JavaScriptLoader: no source_directory given!");
+ }
+
+ if ($app->config->{TT2Renderer}) {
+ my $tt2r = $app->config->{TT2Renderer}->{self};
+ $tt2r->add_NS_function('load_js', sub {
+ my ($c, $name) = @_;
+ my $content = $self->get_content($name);
+ if ($content) {
+ return "<script src='$content->{path}'></script>";
+ }
+ $self->{app}->log->warn("JavaScriptLoader: cannot load $name");
+ return '';
+ });
+ }
+
+ my $r = $app->routes;
+ $r->any('/js_cache/*content_path' => sub {
+ my $c = shift;
+ my $path = $c->stash('content_path');
+ if ($path) {
+ my $content = $self->get_cache($path);
+ if (!$content) {
+ # no cache, so try to generate
+ my ($basename, $md5) = $self->_split_md5($path);
+ $content = $self->get_content($basename);
+
+ # check md5
+ if ($content && $content->{md5} ne $md5) {
+ $content = "";
+ }
+ }
+ if ($content) {
+ $c->res->headers->content_type($content->{type});
+ $c->res->body(encode_utf8($content->{content}));
+ $c->rendered(200);
+ return;
+ }
+ }
+ $c->rendered(404);
+ });
+
+ $app->helper(javascript_loader => sub { state $javascript_loader = $self; });
+}
+
+
+
+1;