OSDN Git Service

Plugin::JavaScriptLoader: register 'load_js' as helper
[newslash/newslash.git] / src / newslash_web / lib / Newslash / Plugin / JavaScriptLoader.pm
1 package Newslash::Plugin::JavaScriptLoader;
2 use Mojo::Base 'Mojolicious::Plugin';
3
4 use File::Spec;
5 use File::Basename qw(fileparse);
6 use Digest::MD5 qw(md5_hex);
7 use IPC::Open3;
8 use Symbol qw(gensym);
9 use IO::Select;
10 use POSIX ":sys_wait_h";
11 use Mojo::Util qw(dumper);
12 use Encode;
13
14 use constant KVS_PREFIX => "JSL:";
15 use constant DEFAULT_MEM_EXPIRE => 60;
16 use constant DEFAULT_KVS_EXPIRE => 60;
17
18 sub _kvs {
19     return shift->{app}->kvs;
20 }
21
22 sub _cache {
23     return shift->{app}->cache;
24 }
25
26 sub set_cache {
27     my ($self, $key, $value) = @_;
28     my $mem_expire = $self->{conf}->{mem_expire};
29     my $kvs_expire = $self->{conf}->{kvs_expire};
30     $mem_expire = DEFAULT_MEM_EXPIRE if !defined $mem_expire;
31     $kvs_expire = DEFAULT_KVS_EXPIRE if !defined $kvs_expire;
32
33     $self->_kvs->set(KVS_PREFIX . $key, $value, $kvs_expire);
34     $self->_cache->set(KVS_PREFIX . $key, $value, $mem_expire);
35 }
36
37 sub get_cache {
38     my ($self, $key) = @_;
39     my $expire = $self->{conf}->{mem_expire};
40     $expire = DEFAULT_MEM_EXPIRE if !defined $expire;
41
42     my $value = $self->_cache->get(KVS_PREFIX . $key);
43     return $value if defined $value;
44
45     $value = $self->_kvs->get(KVS_PREFIX . $key);
46     if (defined $value) {
47         $self->_cache->set(KVS_PREFIX . $key, $value, $expire);
48         return $value;
49     }
50
51     # no cache found
52     return;
53 }
54
55 sub get_content {
56     my ($self, $basename, $options) = @_;
57     my $content = $self->get_cache($basename);
58     return $content if defined $content;
59
60     $content = $self->_load_js($basename, $options);
61     if (defined $content) {
62         $self->set_cache($basename, $content);
63         $self->set_cache($content->{path}, $content);
64         return $content
65     }
66     return;
67 }
68
69 sub cached_basedir {
70     return "/js_cache";
71 }
72
73 sub _load_js {
74     my ($self, $basename, $options) = @_;
75
76     # check if file exists
77     my $abs_path = $self->_basename_to_absolute_path($basename);
78     return if (!-f $abs_path || !-r $abs_path);
79
80     # load file
81     open( my $fh, "<:utf8", $abs_path ) or return;
82     my $js_body = do { local($/); <$fh> } ;
83     close($fh);
84
85     my $md5;
86     if (utf8::is_utf8($js_body)) {
87         $md5 = md5_hex(Encode::encode('utf8', $js_body));
88     }
89     else {
90         $md5 = md5_hex($js_body);
91     }
92     my $path;
93     if ($self->{mode} eq "development") {
94         my $basedir = $self->{conf}->{source_directory};
95         $basedir =~ s/^public//g;
96         $path = "$basedir/$basename";
97     }
98     else {
99         my ($base, $dir, $ext) = fileparse($basename, qw/\.[^.]*/);
100         $dir = "" if $dir eq "./";
101         my $basedir = $self->cached_basedir;
102         $path = "$basedir/$dir${base}.$md5$ext";
103     }
104
105     my $result = { md5 => $md5,
106                    content => $js_body,
107                    path => $path,
108                    type => "text/javascript",
109                  };
110     return $result;
111 }
112
113 sub _basename_to_absolute_path {
114     my ($self, $basename) = @_;
115     my $base_dir = $self->{conf}->{base_directory};
116     if (!$base_dir) {
117         $base_dir = $self->{app}->home;
118     }
119     if (!$base_dir) { # fallback
120         $base_dir = "./";
121     }
122     my $src_dir = $self->{conf}->{source_directory};
123     my $path = File::Spec->catdir($base_dir, $src_dir, $basename);
124     $path = File::Spec->rel2abs($path);
125     return $path;
126 }
127
128 sub _split_md5 {
129     my ($self, $path) = @_;
130     if ($path =~ m/^(.+)\.([0-9a-f]+)(\.\w+)$/) {
131         return ($1 . $3, $2);
132     }
133     return ($path, "");
134 }
135
136 sub register {
137     my ($self, $app, $conf) = @_;
138
139     my $c = $app->config->{JavaScriptLoader};
140     if (!$c) {
141         $app->log->error("JavaScriptLoader: no config settings found!");
142         return;
143     }
144
145     $self->{conf} = {%$conf, %$c};
146     $self->{app} = $app;
147     $self->{mode} = $self->{conf}->{mode} || $app->mode;
148
149     if (!$self->{conf}->{source_directory}) {
150         $app->log->warn("JavaScriptLoader: no source_directory given!");
151     }
152
153     $app->helper('load_js', sub {
154                      my ($c, $name) = @_;
155                      my $content = $self->get_content($name);
156                      if ($content) {
157                          return "<script src='$content->{path}'></script>";
158                      }
159                      $self->{app}->log->warn("JavaScriptLoader: cannot load $name");
160                      return '';
161                  });
162
163     my $r = $app->routes;
164     $r->any('/js_cache/*content_path' => sub {
165                 my $c = shift;
166                 my $path = $c->stash('content_path');
167                 if ($path) {
168                     my $content = $self->get_cache($path);
169                     if (!$content) {
170                         # no cache, so try to generate
171                         my ($basename, $md5) = $self->_split_md5($path);
172                         $content = $self->get_content($basename);
173
174                         # check md5
175                         if ($content && $content->{md5} ne $md5) {
176                             $content = "";
177                         }
178                     }
179                     if ($content) {
180                         $c->res->headers->content_type($content->{type});
181                         $c->res->body(encode_utf8($content->{content}));
182                         $c->rendered(200);
183                         return;
184                     }
185                 }
186                 $c->rendered(404);
187             });
188
189     $app->helper(javascript_loader => sub { state $javascript_loader = $self; });
190 }
191
192
193
194 1;