Commit | Line | Data |
e30ed59d |
1 | package App::IdiotBox; |
2 | |
3 | use Web::Simple __PACKAGE__; |
d7497a23 |
4 | use FindBin; |
5 | use HTML::Zoom; |
998cc52c |
6 | use HTML::Zoom::FilterBuilder::Template; |
02ea620e |
7 | use List::Util qw(first); |
d7497a23 |
8 | |
9 | { |
10 | package App::IdiotBox::Announcement; |
11 | |
ebba317f |
12 | sub id { shift->{id} } |
d7497a23 |
13 | sub made_at { shift->{made_at} } |
14 | sub bucket { shift->{bucket} } |
15 | sub video_count { shift->{video_count} } |
16 | |
17 | package App::IdiotBox::Bucket; |
18 | |
19 | sub slug { shift->{slug} } |
20 | sub name { shift->{name} } |
d9702c6d |
21 | sub video_count { |
22 | exists $_[0]->{video_count} |
23 | ? $_[0]->{video_count} |
24 | : $_[0]->{videos}->count |
25 | } |
71fd1550 |
26 | sub videos { shift->{videos} } |
27 | |
28 | package App::IdiotBox::Video; |
29 | |
30 | sub slug { shift->{slug} } |
31 | sub name { shift->{name} } |
32 | sub author { shift->{author} } |
33 | sub details { shift->{details} } |
34 | sub bucket { shift->{bucket} } |
998cc52c |
35 | sub file_name { |
36 | (my $s = join(' ', @{+shift}{qw(author name)})) =~ s/ /-/g; |
37 | $s; |
38 | } |
ebba317f |
39 | sub url_path { |
40 | join('/', $_[0]->bucket->slug, $_[0]->slug); |
41 | } |
d7497a23 |
42 | } |
43 | |
8b9d3d54 |
44 | sub default_config { |
71a02d85 |
45 | template_dir => 'share/html', |
46 | store => 'SQLite', |
47 | db_file => 'var/lib/idiotbox.db', |
998cc52c |
48 | base_url => 'http://localhost:3000/', |
ebba317f |
49 | base_dir => do { use FindBin; $FindBin::Bin }, |
8b9d3d54 |
50 | } |
e30ed59d |
51 | |
71a02d85 |
52 | sub BUILD { |
53 | my $self = shift; |
54 | my $store; |
55 | ($store = $self->config->{store}) =~ /^(\w+)$/ |
56 | or die "Store config should be just a name, got ${store} instead"; |
57 | my $store_class = "App::IdiotBox::Store::${store}"; |
58 | eval "require ${store_class}; 1" |
59 | or die "Couldn't load ${store} store: $@"; |
60 | $store_class->bind($self); |
61 | } |
62 | |
8b9d3d54 |
63 | sub dispatch_request { |
64 | my $self = shift; |
e30ed59d |
65 | sub (/) { $self->show_front_page }, |
8b9d3d54 |
66 | sub (/*/...) { |
e30ed59d |
67 | my $bucket = $self->buckets->get({ slug => $_[1] }); |
8b9d3d54 |
68 | sub (/) { |
69 | $self->show_bucket($bucket) |
70 | }, |
71 | sub (/*/) { |
72 | $self->show_video($bucket->videos->get({ slug => $_[1] })); |
73 | } |
e30ed59d |
74 | } |
8b9d3d54 |
75 | } |
e30ed59d |
76 | |
847de56a |
77 | sub recent_announcements { shift->{recent_announcements} } |
d7497a23 |
78 | |
847de56a |
79 | sub buckets { shift->{buckets} } |
71a02d85 |
80 | |
847de56a |
81 | sub show_front_page { |
82 | my $self = shift; |
e30ed59d |
83 | my $ann = $self->recent_announcements; |
84 | $self->html_response( |
1a1c4f78 |
85 | front_page => sub { |
86 | $_->select('#announcement-list') |
87 | ->repeat_content($ann->map(sub { |
88 | my $obj = $_; |
89 | sub { |
90 | $_->select('.bucket-name')->replace_content($obj->bucket->name) |
02ea620e |
91 | ->select('.made-at')->replace_content($obj->made_at) |
1a1c4f78 |
92 | ->select('.bucket-link')->set_attribute({ |
93 | name => 'href', value => $obj->bucket->slug.'/' |
94 | }) |
95 | ->select('.new-videos')->replace_content($obj->video_count) |
96 | ->select('.total-videos')->replace_content( |
97 | $obj->bucket->video_count |
98 | ) |
99 | } |
100 | })) |
101 | } |
e30ed59d |
102 | ); |
103 | } |
104 | |
847de56a |
105 | sub show_bucket { |
106 | my ($self, $bucket) = @_; |
1a1c4f78 |
107 | $self->html_response(bucket => sub { |
108 | $_->select('.bucket-name')->replace_content($bucket->name) |
109 | ->select('#video-list')->repeat_content($bucket->videos->map(sub { |
110 | my $video = $_; |
111 | sub { |
112 | $_->select('.video-name')->replace_content($video->name) |
113 | ->select('.video-author')->replace_content($video->author) |
114 | ->select('.video-link')->set_attribute( |
115 | { name => 'href', value => $video->slug.'/' } |
116 | ) |
117 | } |
118 | })) |
119 | }); |
d7497a23 |
120 | } |
121 | |
847de56a |
122 | sub show_video { |
123 | my ($self, $video) = @_; |
02ea620e |
124 | my $video_file = first { |
125 | -e join('/', $self->config->{base_dir}, $_) |
126 | } map { |
127 | join('/', $video->bucket->slug, $video->slug, $video->file_name.".$_") |
128 | } qw(flv m4v); |
1a1c4f78 |
129 | $self->html_response(video => sub { |
998cc52c |
130 | my $video_url = |
131 | $self->base_url |
6df05090 |
132 | .($video_file||'NO FILE FOUND SORRY'); |
998cc52c |
133 | |
1a1c4f78 |
134 | $_->select('.video-name')->replace_content($video->name) |
135 | ->select('.author-name')->replace_content($video->author) |
136 | ->select('.bucket-link')->set_attribute( |
137 | { name => 'href', value => '../' } |
138 | ) |
139 | ->select('.bucket-name')->replace_content($video->bucket->name) |
140 | ->select('.video-details')->replace_content($video->details) |
998cc52c |
141 | ->select('script')->template_text_raw({ video_url => $video_url }); |
1a1c4f78 |
142 | }); |
d7497a23 |
143 | } |
144 | |
847de56a |
145 | sub html_response { |
146 | my ($self, $template_name, $selectors) = @_; |
1a1c4f78 |
147 | my $io = $self->_zoom_for($template_name => $selectors)->to_fh; |
e30ed59d |
148 | return [ 200, [ 'Content-Type' => 'text/html' ], $io ] |
149 | } |
150 | |
847de56a |
151 | sub _template_filename_for { |
152 | my ($self, $name) = @_; |
d7497a23 |
153 | $self->{config}{template_dir}.'/'.$name.'.html'; |
154 | } |
155 | |
847de56a |
156 | sub _layout_zoom { |
157 | my $self = shift; |
d7497a23 |
158 | $self->{layout_zoom} ||= HTML::Zoom->from_file( |
159 | $self->_template_filename_for('layout') |
e30ed59d |
160 | ) |
161 | } |
162 | |
847de56a |
163 | sub _zoom_for { |
164 | my ($self, $template_name, $selectors) = @_; |
e30ed59d |
165 | ($self->{zoom_for_template}{$template_name} ||= do { |
166 | my @body; |
d7497a23 |
167 | HTML::Zoom->from_file( |
168 | $self->_template_filename_for($template_name) |
e30ed59d |
169 | ) |
1a1c4f78 |
170 | ->select('#main-content')->collect_content({ into => \@body }) |
d7497a23 |
171 | ->run; |
1a1c4f78 |
172 | $self->_layout_zoom |
173 | ->select('#main-content')->replace_content(\@body) |
174 | ->memoize; |
175 | })->apply($selectors); |
e30ed59d |
176 | } |
177 | |
847de56a |
178 | sub base_url { |
179 | my $self = shift; |
998cc52c |
180 | $self->{base_url} ||= do { |
181 | (my $u = $self->config->{base_url}) =~ s/\/$//; |
182 | "${u}/"; |
183 | } |
184 | } |
185 | |
847de56a |
186 | sub _run_cli { |
187 | my $self = shift; |
ebba317f |
188 | unless (@ARGV == 1 && $ARGV[0] eq 'import') { |
189 | return $self->SUPER::_run_cli(@_); |
190 | } |
191 | $self->cli_import; |
192 | } |
193 | |
847de56a |
194 | sub _cli_usage { |
195 | my $self = shift; |
ebba317f |
196 | "To import data into your idiotbox install, chdir into a directory\n". |
197 | "containing video files and run:\n". |
198 | "\n". |
199 | " $0 import\n". |
200 | "\n". |
201 | $self->SUPER::_cli_usage(@_); |
202 | } |
203 | |
847de56a |
204 | sub cli_import { |
205 | my $self = shift; |
ebba317f |
206 | require App::IdiotBox::Importer; |
207 | App::IdiotBox::Importer->run($self); |
208 | } |
209 | |
e30ed59d |
210 | 1; |