File Coverage

blib/lib/Sakai/Nakamura/Content.pm
Criterion Covered Total %
statement 101 224 45.0
branch 22 68 32.3
condition 0 3 0.0
subroutine 20 28 71.4
pod 10 15 66.6
total 153 338 45.2


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             package Sakai::Nakamura::Content;
4              
5 1     1   1422 use 5.008008;
  1         4  
  1         37  
6 1     1   6 use strict;
  1         1  
  1         34  
7 1     1   5 use warnings;
  1         1  
  1         24  
8 1     1   5 use Carp;
  1         1  
  1         59  
9 1     1   9151 use JSON;
  1         26317  
  1         6  
10 1     1   2123 use Getopt::Long qw(:config bundling);
  1         17641  
  1         6  
11 1     1   6283 use Pod::Usage;
  1         110932  
  1         194  
12 1     1   13 use Sakai::Nakamura;
  1         3  
  1         36  
13 1     1   6 use Sakai::Nakamura::Authn;
  1         24  
  1         31  
14 1     1   886 use Sakai::Nakamura::ContentUtil;
  1         3  
  1         53  
15              
16 1     1   5 use base qw(Apache::Sling::Content);
  1         3  
  1         1054  
17              
18             require Exporter;
19              
20 1     1   17099 use base qw(Exporter);
  1         4  
  1         2952  
21              
22             our @EXPORT_OK = qw(run);
23              
24             our $VERSION = '0.13';
25              
26             #{{{sub new
27             sub new {
28 2     2 1 1650 my ( $class, @args ) = @_;
29 2         24 my $content = $class->SUPER::new(@args);
30              
31             # Add a class variable to track the last content path seen:
32 1         39 $content->{'Path'} = q{};
33              
34             # Add a class variable to track the last comment made:
35 1         5 $content->{'Comment'} = q{};
36 1         3 bless $content, $class;
37 1         5 return $content;
38             }
39              
40             #}}}
41              
42             #{{{ sub command_line
43             sub command_line {
44 0     0 0 0 my ( $class, @ARGV ) = @_;
45 0         0 my $nakamura = Sakai::Nakamura->new;
46 0         0 my $config = $class->config( $nakamura, @ARGV );
47 0         0 my $authn = new Sakai::Nakamura::Authn( \$nakamura );
48 0         0 return $class->run( $nakamura, $config );
49             }
50              
51             #}}}
52              
53             #{{{sub comment_add
54             sub comment_add {
55 0     0 0 0 my ( $content, $comment, $remote_dest ) = @_;
56 0 0       0 $remote_dest =
57             defined $remote_dest
58             ? Apache::Sling::URL::strip_leading_slash($remote_dest)
59             : $content->{'Path'};
60              
61 0         0 my $res = Apache::Sling::Request::request(
62             \$content,
63             Sakai::Nakamura::ContentUtil::comment_add_setup(
64             $content->{'BaseURL'}, $remote_dest, $comment
65             )
66             );
67 0         0 my $success = Sakai::Nakamura::ContentUtil::comment_add_eval($res);
68 0 0       0 my $message = (
69             $success
70             ? 'Comment added'
71             : 'Problem adding comment to content'
72             );
73 0         0 $content->set_results( "$message", $res );
74 0         0 return $success;
75             }
76              
77             #}}}
78              
79             #{{{sub config
80              
81             sub config {
82 1     1 1 5 my ( $class, $nakamura, @ARGV ) = @_;
83 1         7 my $content_config = $class->config_hash( $nakamura, @ARGV );
84              
85 1 50       11 GetOptions(
86             $content_config, 'auth=s',
87             'help|?', 'log|L=s',
88             'man|M', 'pass|p=s',
89             'threads|t=s', 'url|U=s',
90             'user|u=s', 'verbose|v+',
91             'add|a', 'additions|A=s',
92             'copy|c', 'delete|d',
93             'exists|e', 'filename|n=s',
94             'local|l=s', 'move|m',
95             'property|P=s', 'remote|r=s',
96             'remote-source|S=s', 'replace|R',
97             'view|V', 'view-copyright=s',
98             'view-description=s', 'view-tags=s',
99             'view-title=s', 'view-visibility=s'
100             ) or $class->help();
101              
102 1         2225 return $content_config;
103             }
104              
105             #}}}
106              
107             #{{{sub config_hash
108              
109             sub config_hash {
110 1     1 0 3 my ( $class, $nakamura, @ARGV ) = @_;
111 1         3 my $view_copyright;
112             my $view_description;
113 0         0 my $view_tags;
114 0         0 my $view_title;
115 0         0 my $view_visibility;
116 1         15 my $content_config = $class->SUPER::config_hash( $nakamura, @ARGV );
117 1         51 $content_config->{'view-copyright'} = \$view_copyright;
118 1         3 $content_config->{'view-description'} = \$view_description;
119 1         3 $content_config->{'view-tags'} = \$view_tags;
120 1         5 $content_config->{'view-title'} = \$view_title;
121 1         3 $content_config->{'view-visibility'} = \$view_visibility;
122              
123 1         4 return $content_config;
124             }
125              
126             #}}}
127              
128             #{{{ sub help
129             sub help {
130              
131 1     1 0 1896 print <<"EOF";
132             Usage: perl $0 [-OPTIONS [-MORE_OPTIONS]] [--] [PROGRAM_ARG1 ...]
133             The following options are accepted:
134              
135             --additions or -A (file) - File containing list of content to be uploaded.
136             --add or -a - Add content.
137             --auth (type) - Specify auth type. If ommitted, default is used.
138             --copy or -c - Copy content.
139             --delete or -d - Delete content.
140             --filename or -n (filename) - Specify file name to use for content upload.
141             --help or -? - view the script synopsis and options.
142             --local or -l (localPath) - Local path to content to upload.
143             --log or -L (log) - Log script output to specified log file.
144             --man or -M - view the full script documentation.
145             --move or -m - Move content.
146             --pass or -p (password) - Password of user performing content manipulations.
147             --property or -P (property) - Specify property to set on node.
148             --remote or -r (remoteNode) - specify remote destination under JCR root to act on.
149             --remote-source or -S (remoteSrc) - specify remote source node under JCR root to act on.
150             --replace or -R - when copying or moving, overwrite remote destination if it exists.
151             --threads or -t (threads) - Used with -A, defines number of parallel
152             processes to have running through file.
153             --url or -U (URL) - URL for system being tested against.
154             --user or -u (username) - Name of user to perform content manipulations as.
155             --verbose or -v or -vv or -vvv - Increase verbosity of output.
156             --view or -V (actOnContent) - view details for specified content in json format.
157             --view-copyright (remoteNode) - view copyright for specified remote content.
158             --view-description (remoteNode) - view description for specified remote content.
159             --view-tags (remoteNode) - view tags for specified remote content.
160             --view-title (remoteNode) - view title for specified remote content.
161             --view-visibility (remoteNode) - view visibility setting for specified remote content.
162              
163             Options may be merged together. -- stops processing of options.
164             Space is not required between options and their arguments.
165             For full details run: perl $0 --man
166             EOF
167              
168 1         5 return 1;
169              
170             }
171              
172             #}}}
173              
174             #{{{sub run
175             sub run {
176 2     2 1 63 my ( $content, $nakamura, $config ) = @_;
177 2 100       8 if ( !defined $config ) {
178 1         35 croak 'No content config supplied!';
179             }
180 1         12 $nakamura->check_forks;
181 1         7 ${ $config->{'remote'} } =
  1         9  
182 1         70 Apache::Sling::URL::strip_leading_slash( ${ $config->{'remote'} } );
183 1         6 ${ $config->{'remote-source'} } = Apache::Sling::URL::strip_leading_slash(
  1         4  
184 1         2 ${ $config->{'remote-source'} } );
185 1         3 my $authn =
186             defined $nakamura->{'Authn'}
187 1 50       5 ? ${ $nakamura->{'Authn'} }
188             : new Sakai::Nakamura::Authn( \$nakamura );
189              
190 1         3 my $success = 1;
191              
192 1 50       6 if ( $nakamura->{'Help'} ) { $content->help(); }
  0 50       0  
    50          
193 0         0 elsif ( $nakamura->{'Man'} ) { $content->man(); }
  1         6  
194             elsif ( defined ${ $config->{'additions'} } ) {
195 0         0 my $message =
196 0         0 "Adding content from file \"" . ${ $config->{'additions'} } . "\":\n";
197 0         0 Apache::Sling::Print::print_with_lock( "$message", $nakamura->{'Log'} );
198 0         0 my @childs = ();
199 0         0 for my $i ( 0 .. $nakamura->{'Threads'} ) {
200 0         0 my $pid = fork;
201 0 0       0 if ($pid) { push @childs, $pid; } # parent
  0 0       0  
202             elsif ( $pid == 0 ) { # child
203             # Create a new separate user agent per fork in order to
204             # ensure cookie stores are separate, then log the user in:
205 0         0 $authn->{'LWP'} = $authn->user_agent( $nakamura->{'Referer'} );
206 0         0 $authn->login_user();
207 0         0 my $content =
208             new Sakai::Nakamura::Content( \$authn, $nakamura->{'Verbose'},
209             $nakamura->{'Log'} );
210 0         0 $content->upload_from_file( ${ $config->{'additions'} },
  0         0  
211             $i, $nakamura->{'Threads'} );
212 0         0 exit 0;
213             }
214             else {
215 0         0 croak "Could not fork $i!";
216             }
217             }
218 0         0 foreach (@childs) { waitpid $_, 0; }
  0         0  
219             }
220             else {
221 1 50       2 if ( defined ${ $config->{'local'} } ) {
  1 50       5  
  1 50       5  
    50          
    50          
    50          
222 0         0 $authn->login_user();
223 0         0 $content =
224             new Sakai::Nakamura::Content( \$authn, $nakamura->{'Verbose'},
225             $nakamura->{'Log'} );
226 0         0 $success = $content->upload_file( ${ $config->{'local'} } );
  0         0  
227 0         0 Apache::Sling::Print::print_result($content);
228             }
229 1         3 elsif ( defined ${ $config->{'view-copyright'} } ) {
230 0         0 $authn->login_user();
231 0         0 $content =
232             new Sakai::Nakamura::Content( \$authn, $nakamura->{'Verbose'},
233             $nakamura->{'Log'} );
234 0         0 $success =
235 0         0 $content->view_copyright( ${ $config->{'view-copyright'} } );
236 0         0 Apache::Sling::Print::print_result($content);
237             }
238 1         5 elsif ( defined ${ $config->{'view-description'} } ) {
239 0         0 $authn->login_user();
240 0         0 $content =
241             new Sakai::Nakamura::Content( \$authn, $nakamura->{'Verbose'},
242             $nakamura->{'Log'} );
243 0         0 $success =
244 0         0 $content->view_description( ${ $config->{'view-description'} } );
245 0         0 Apache::Sling::Print::print_result($content);
246             }
247 1         4 elsif ( defined ${ $config->{'view-tags'} } ) {
248 0         0 $authn->login_user();
249 0         0 $content =
250             new Sakai::Nakamura::Content( \$authn, $nakamura->{'Verbose'},
251             $nakamura->{'Log'} );
252 0         0 $success = $content->view_tags( ${ $config->{'view-tags'} } );
  0         0  
253 0         0 Apache::Sling::Print::print_result($content);
254             }
255 1         12 elsif ( defined ${ $config->{'view-title'} } ) {
256 0         0 $authn->login_user();
257 0         0 $content =
258             new Sakai::Nakamura::Content( \$authn, $nakamura->{'Verbose'},
259             $nakamura->{'Log'} );
260 0         0 $success = $content->view_title( ${ $config->{'view-title'} } );
  0         0  
261 0         0 Apache::Sling::Print::print_result($content);
262             }
263             elsif ( defined ${ $config->{'view-visibility'} } ) {
264 0         0 $authn->login_user();
265 0         0 $content =
266             new Sakai::Nakamura::Content( \$authn, $nakamura->{'Verbose'},
267             $nakamura->{'Log'} );
268 0         0 $success =
269 0         0 $content->view_visibility( ${ $config->{'view-visibility'} } );
270 0         0 Apache::Sling::Print::print_result($content);
271             }
272             else {
273 1         19 $success = $content->SUPER::run( $nakamura, $config );
274             }
275             }
276 1         12 return $success;
277             }
278              
279             #}}}
280              
281             #{{{sub upload_file
282             sub upload_file {
283 1     1 1 3971 my ( $content, $local_path ) = @_;
284 1         4 my $filename = q{};
285 1         9 my $res = Apache::Sling::Request::request(
286             \$content,
287             Apache::Sling::ContentUtil::upload_file_setup(
288             $content->{'BaseURL'}, $local_path,
289             'system/pool/createfile', $filename
290             )
291             );
292 0         0 my $success = Apache::Sling::ContentUtil::upload_file_eval($res);
293              
294             # Check whether initial upload succeeded:
295 0 0       0 if ( !$success ) {
296 0         0 croak
297             "Content: \"$local_path\" upload to /system/pool/createfile failed!";
298             }
299              
300             # Obtain path from POST response body:
301 0         0 my $content_path = ( ${$res}->content =~ m/"_path":"([^"]*)"/x )[0];
  0         0  
302 0 0       0 if ( !defined $content_path ) {
303 0         0 croak 'Content path not found in JSON response to file upload';
304             }
305 0         0 $content_path = "p/$content_path";
306 0         0 my $content_filename =
307 0         0 ( ${$res}->content =~ m/"sakai:pooled-content-file-name":"([^"]*)"/x )[0];
308 0 0       0 if ( !$content_filename =~ /.*\..*/x ) {
309 0         0 croak "Content filename: '$content_filename' has no file extension";
310             }
311 0         0 my $content_fileextension = ( $content_filename =~ m/([^.]+)$/x )[0];
312              
313             # Add Meta data for file:
314 0         0 $res = Apache::Sling::Request::request(
315             \$content,
316             Sakai::Nakamura::ContentUtil::add_file_metadata_setup(
317             $content->{'BaseURL'}, "$content_path",
318             $content_filename, $content_fileextension
319             )
320             );
321 0         0 $success = Sakai::Nakamura::ContentUtil::add_file_metadata_eval($res);
322              
323             # Check whether adding metadata succeeded:
324 0 0       0 if ( !$success ) {
325 0         0 croak "Adding metadata for \"$content_path\" failed!";
326             }
327              
328             # Add permissions on file:
329 0         0 $res = Apache::Sling::Request::request(
330             \$content,
331             Sakai::Nakamura::ContentUtil::add_file_perms_setup(
332             $content->{'BaseURL'}, "$content_path"
333             )
334             );
335 0         0 $success = Sakai::Nakamura::ContentUtil::add_file_perms_eval($res);
336              
337             # Check whether setting file permissions succeeded:
338 0 0       0 if ( !$success ) {
339 0         0 croak "Adding file perms for \"$content_path\" failed!";
340             }
341 0         0 my $message =
342             "File upload of \"$local_path\" to \"$content_path\" succeeded";
343 0         0 $content->set_results( "$message", $res );
344 0         0 $content->{'Path'} = $content_path;
345 0         0 return $success;
346             }
347              
348             #}}}
349              
350             #{{{sub upload_from_file
351             sub upload_from_file {
352 5     5 1 1921 my ( $content, $file, $fork_id, $number_of_forks ) = @_;
353 5 50       15 $fork_id = defined $fork_id ? $fork_id : 0;
354 5 50       11 $number_of_forks = defined $number_of_forks ? $number_of_forks : 1;
355 5         7 my $count = 0;
356 5 100       13 if ( !defined $file ) {
357 2         18 croak 'File to upload from not defined';
358             }
359 1 100   1   13 if ( open my ($input), '<', $file ) {
  1         2  
  1         9  
  3         294  
360 2         1901 while (<$input>) {
361 1 50       7 if ( $fork_id == ( $count++ % $number_of_forks ) ) {
362 1         5 chomp;
363 1 50       37 $_ =~ /^\s*(\S.*?)\s*$/msx
364             or croak "/Problem parsing content to add: '$_'";
365 0         0 my $local_path = $1;
366 0         0 $content->upload_file($local_path);
367 0         0 Apache::Sling::Print::print_result($content);
368             }
369             }
370 1 50       18 close $input or croak 'Problem closing input!';
371             }
372             else {
373 1         21 croak "Problem opening file: '$file'";
374             }
375 1         14 return 1;
376             }
377              
378             #}}}
379              
380             #{{{sub view_attribute
381             sub view_attribute {
382 0     0 0   my ( $content, $remote_dest, $attribute_name, $nakamura_name, $missing_ok )
383             = @_;
384 0 0         $remote_dest =
385             defined $remote_dest
386             ? Apache::Sling::URL::strip_leading_slash($remote_dest)
387             : $content->{'Path'};
388              
389             # By default the attribute must be present in the full JSON:
390 0 0         $missing_ok = defined $missing_ok ? $missing_ok : 0;
391 0           my $json_success = $content->view_full_json($remote_dest);
392 0 0         if ( !$json_success ) {
393 0           return $json_success;
394             }
395 0           my $content_json = from_json( $content->{'Message'} );
396 0           my $attribute = $content_json->{$nakamura_name};
397              
398             # merge an array attribute into a string:
399 0 0         if ( ref($attribute) eq 'ARRAY' ) {
400 0           $attribute = join( ',', @{$attribute} );
  0            
401             }
402              
403             # If the attribute is undefined but allowed to be
404             # missing then set it to an empty string:
405 0 0 0       if ( !defined $attribute && $missing_ok ) {
406 0           $attribute = q{};
407             }
408 0           my $success = defined $attribute;
409 0 0         $content->{'Message'} =
410             $success ? $attribute : "Problem viewing $attribute_name";
411 0           return $success;
412             }
413              
414             #}}}
415              
416             #{{{sub view_copyright
417             sub view_copyright {
418 0     0 1   my ( $content, $remote_dest ) = @_;
419 0           my $success =
420             $content->view_attribute( $remote_dest, 'copyright', 'sakai:copyright' );
421 0           return $success;
422             }
423              
424             #}}}
425              
426             #{{{sub view_description
427             sub view_description {
428 0     0 1   my ( $content, $remote_dest ) = @_;
429 0           my $success = $content->view_attribute( $remote_dest, 'description',
430             'sakai:description', 1 );
431 0           return $success;
432             }
433              
434             #}}}
435              
436             #{{{sub view_tags
437             sub view_tags {
438 0     0 1   my ( $content, $remote_dest ) = @_;
439 0           my $success =
440             $content->view_attribute( $remote_dest, 'tags', 'sakai:tags', 1 );
441 0           return $success;
442             }
443              
444             #}}}
445              
446             #{{{sub view_title
447             sub view_title {
448 0     0 1   my ( $content, $remote_dest ) = @_;
449 0           my $success = $content->view_attribute( $remote_dest, 'title',
450             'sakai:pooled-content-file-name' );
451 0           return $success;
452             }
453              
454             #}}}
455              
456             #{{{sub view_visibility
457             sub view_visibility {
458 0     0 1   my ( $content, $remote_dest ) = @_;
459 0           my $success = $content->view_attribute( $remote_dest, 'visibility',
460             'sakai:permissions' );
461 0           return $success;
462             }
463              
464             #}}}
465              
466             1;
467              
468             __END__