diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 548d24991..4e97e4e85 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -18,6 +18,7 @@ - fixed: Story subtitle is supposed to be a text field, not a text area. - fixed: Display of short pagination bar when there's less than 1 page of data in StoryArchive - added: Storage now has a getHexId method for returning a cached hexadecimal version of the storageId. + - fixed: FilePump should copy filesystem directories and their files when given a file URI that is a directory. 7.7.6 - Added mobile style template. If enabled in settings, will serve alternate style templates diff --git a/lib/WebGUI/FilePump/Bundle.pm b/lib/WebGUI/FilePump/Bundle.pm index e7f4f3f68..01de31791 100644 --- a/lib/WebGUI/FilePump/Bundle.pm +++ b/lib/WebGUI/FilePump/Bundle.pm @@ -1,6 +1,7 @@ package WebGUI::FilePump::Bundle; use base qw/WebGUI::Crud/; +use strict; use WebGUI::International; use WebGUI::Utility; use URI; @@ -120,7 +121,7 @@ sub build { ##Create the new build directory my $newDir = $self->getPathClassDir($newBuild); my $mkpathErrors; - my $dirsCreated = $newDir->mkpath({ errors => $mkpathErrors }); + my $dirsCreated = $newDir->mkpath({ errors => \$mkpathErrors }); if (! $dirsCreated) { $newDir->rmtree; my $errorMessages = join "\n", @{ $mkpathErrors }; @@ -129,7 +130,6 @@ sub build { ##Copy files over my $otherFiles = $self->get('otherFiles'); - my $i18n = WebGUI::International->new($self->session, 'FilePump'); OTHERFILE: foreach my $file (@{ $otherFiles }) { my $uri = $file->{uri}; my $results = $self->fetch($uri); @@ -138,18 +138,14 @@ sub build { last OTHERFILE; } $file->{lastModified} = $results->{lastModified}; - my $uriPath = URI->new($uri)->opaque; - $uriPath =~ tr{/}{/}s; - my $filename = basename($uriPath); - my $newFile = $newDir->file($filename); - if (-e $newFile->stringify) { - $error = join ' ', $uri, $i18n->get('duplicate file'); - last OTHERFILE; + if ($results->{type} eq 'file') { + $error = $self->_buildFile($newDir, $uri, $results); } - my $fh = $newFile->open('>'); - $fh->binmode; - print $fh $results->{content}; - close $fh; + elsif ($results->{type} eq 'directory') { + $error = $self->_buildDir($newDir, $uri, $results); + } + last OTHERFILE if ($error); + } if ($error) { @@ -188,6 +184,102 @@ sub build { #------------------------------------------------------------------- +=head2 _buildDir ( $newDir, $uri, $results ) + +Copy over a directory and all its files from the filesystem into the build directory. It does +not copy deeply. + +=head3 $newDir + +A Path::Class::Dir object pointing to the new build directory. + +=head3 $uri + +A URI to the original directory. + +=head3 $results + +The returned results from fetchDir, with the file contents to +install into the build directory. + +=cut + +sub _buildDir { + my ($self, $newDir, $uri, $results) = @_; + my $uriPath = URI->new($uri)->opaque; + $uriPath =~ tr{/}{/}s; + my $uriDir = Path::Class::Dir->new($uriPath); + my $dirname = $uriDir->dir_list(-1, 1); + my $newSubDir = $newDir->subdir($dirname); + if (-e $newSubDir->stringify) { + my $i18n = WebGUI::International->new($self->session, 'FilePump'); + return join ' ', $uri, $i18n->get('duplicate directory'); + } + my $mkpathErrors; + my $dirsCreated = $newSubDir->mkpath({ errors => \$mkpathErrors }); + if (! $dirsCreated) { + $newSubDir->rmtree; + my $errorMessages = join "\n", @{ $mkpathErrors }; + return $errorMessages; + } + ##Note, we built the directory, so there should be no problems with + ##file permissions. Likewise, since you can't have files with the same + ##name in the source directory, there's no need to check for filename collisions. + foreach my $subFile (@{ $results->{content} }) { + my $inFH = $subFile->open('<'); + my $newFile = $newSubDir->file($subFile->basename); + my $outFH = $newFile->open('>'); + $inFH->binmode; + $outFH->binmode; + local $/; + my $inFile = <$inFH>; + print $outFH $inFile; + $inFH->close; + $outFH->close; + } + return 0; +} + +#------------------------------------------------------------------- + +=head2 _buildFile ( $newDir, $uri, $results ) + +Copy over a file from the filesystem into the build directory. + +=head3 $newDir + +A Path::Class::Dir object pointing to the new build directory. + +=head3 $uri + +A URI to the original file. + +=head3 $results + +The returned results from fetchFile, with the file contents to +install into the build directory. + +=cut + +sub _buildFile { + my ($self, $newDir, $uri, $results) = @_; + my $uriPath = URI->new($uri)->opaque; + $uriPath =~ tr{/}{/}s; + my $filename = basename($uriPath); + my $newFile = $newDir->file($filename); + if (-e $newFile->stringify) { + my $i18n = WebGUI::International->new($self->session, 'FilePump'); + return join ' ', $uri, $i18n->get('duplicate file'); + } + my $fh = $newFile->open('>'); + $fh->binmode; + print $fh $results->{content}; + close $fh; + return 0; +} + +#------------------------------------------------------------------- + =head2 crud_definition WebGUI::Crud definition for this class. @@ -460,11 +552,46 @@ sub fetchAsset { #------------------------------------------------------------------- +=head2 fetchDirectory ( $uri ) + +Fetches all files from a filesystem directory. Returns a hashref +with the date that the directory was last updated, a contents entry +which is an arrayref of Path::Class objects from the directory. '.', and '../' +are always ommitted, and a type entry which is the string 'directory'. + +If there is any problem with getting files, it returns an empty hashref. + +=head3 $uri + +A URI object. + +=cut + +sub fetchDir { + my ($self, $uri ) = @_; + my $filepath = $uri->path; + return {} unless (-e $filepath && -r _ && -d _); + my @stats = stat(_); + my $dir = Path::Class::Dir->new($filepath); + my $guts = { + lastModified => $stats[9], + content => [ $dir->children ], + type => 'directory', + }; + return $guts; +} + +#------------------------------------------------------------------- + =head2 fetchFile ( $uri ) -Fetches a bundle file from the local filesystem. Returns a hashref -with the content and date that it was last updated. If there is any problem -with getting the file, it returns an empty hashref. +Fetches a bundle file from the local filesystem. Returns a hashref with the +content, date that it was last updated, and a type entry which is the string +'file'. If there is any problem with getting the file, it returns an +empty hashref. + +If fetchFile is passed a URI which is a directory, it will call fetchDir on +that URI and return the results. =head3 $uri @@ -476,12 +603,14 @@ sub fetchFile { my ($self, $uri ) = @_; my $filepath = $uri->path; return {} unless (-e $filepath && -r _); + return $self->fetchDir($uri) if -d _; my @stats = stat(_); # recycle stat data from file tests. open my $file, '<', $filepath or return {}; local $/; my $guts = { lastModified => $stats[9], content => <$file>, + type => 'file', }; close $file; return $guts; diff --git a/lib/WebGUI/i18n/English/FilePump.pm b/lib/WebGUI/i18n/English/FilePump.pm index 55391d96e..a5809c34e 100644 --- a/lib/WebGUI/i18n/English/FilePump.pm +++ b/lib/WebGUI/i18n/English/FilePump.pm @@ -106,6 +106,12 @@ our $I18N = { context => q|Error message when building a new bundle.| }, + 'duplicate directory' => { + message => q|A directory with the same name already exists in the build directory.|, + lastUpdated => 1242515308, + context => q|Error message when building a new bundle.| + }, + }; 1; diff --git a/t/FilePump/Bundle.t b/t/FilePump/Bundle.t index 825be252b..1dde9211f 100644 --- a/t/FilePump/Bundle.t +++ b/t/FilePump/Bundle.t @@ -33,7 +33,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 50; # Increment this number for each test you create +my $tests = 54; # Increment this number for each test you create plan tests => 1 + $tests; # 1 for the use_ok #---------------------------------------------------------------------------- @@ -240,6 +240,11 @@ my $fileAsset = $root->addChild({ $fileAsset->getStorageLocation->addFileFromScalar('pumpfile', 'Pump up the jam'); +my $storage = WebGUI::Storage->create($session); +WebGUI::Test->storagesToDelete($storage); +$storage->addFileFromScalar('addendum', 'Red was too'); +$storage->addFileFromFilesystem(WebGUI::Test->getTestCollateralPath('ShawshankRedemptionMoviePoster.jpg')); + my $snippetTag = WebGUI::VersionTag->getWorking($session); WebGUI::Test->tagsToRollback($snippetTag); $snippetTag->commit; @@ -274,10 +279,26 @@ cmp_deeply( { content => 'Pump up the jam', lastModified => re('^\d+$'), + type => 'file', }, 'fetchFile: retrieved a file from the filesystem' ); +my $uriDir = URI->new('file:'.$storage->getPath); +$guts = $bundle->fetchDir($uriDir); +cmp_deeply( + $guts, + { + lastModified => re('^\d+$'), + content => [ + isa('Path::Class::File'), + isa('Path::Class::File'), + ], + type => 'directory', + }, + 'fetchDir: retrieved information about a directory and its subfiles from the filesystem' +); + ################################################################### # # getPathClassDir @@ -335,6 +356,7 @@ $fileAsset->update({filename => 'pumpfile.css'}); $bundle->addFile('JS', 'asset://filePumpSnippet'); $bundle->addFile('CSS', 'asset://filePumpFileAsset'); $bundle->addFile('OTHER', 'file:'.WebGUI::Test->getTestCollateralPath('gooey.jpg')); +$bundle->addFile('OTHER', 'file:'.$storage->getPath); my ($buildFlag, $error) = $bundle->build(); ok($buildFlag, 'build returns true when there are no errors'); diag $error unless $buildFlag; @@ -347,13 +369,22 @@ ok(!-e $oldBuildDir->stringify && !-d _, '... old build directory deleted'); my $jsFile = $buildDir->file($bundle->bundleUrl . '.js'); my $cssFile = $buildDir->file($bundle->bundleUrl . '.css'); my $otherFile = $buildDir->file('gooey.jpg'); +my $otherDir = $buildDir->subdir($storage->getHexId); ok(-e $jsFile->stringify && -f _ && -s _, '... minified JS file built, not empty'); ok(-e $cssFile->stringify && -f _ && -s _, '... minified CSS file built, not empty'); ok(-e $otherFile->stringify && -f _ && -s _, '... other file copied over, not empty'); +ok(-e $otherDir->stringify && -d _ , '... other directory copied over'); + +cmp_deeply( + [ map { $_->basename } $otherDir->children ], + [ qw/addendum ShawshankRedemptionMoviePoster.jpg/ ], + '... File copied over to new directory' +); ok($bundle->get('jsFiles')->[0]->{lastModified}, '... updated JS file lastModified'); ok($bundle->get('cssFiles')->[0]->{lastModified}, '... updated CSS file lastModified'); ok($bundle->get('otherFiles')->[0]->{lastModified}, '... updated OTHER file lastModified'); +ok($bundle->get('otherFiles')->[1]->{lastModified}, '... updated OTHER directory lastModified'); ################################################################### #