Changeset 580

Show
Ignore:
Timestamp:
07/02/08 13:55:58 (3 months ago)
Author:
mgranger
Message:
  • Image plugin:
    • Updated to generate and append thumbnails.
    • Refactored the calculation of the supported formats.
  • MP3 plugin:
    • Modified to accept 'audio/mpg' and 'audio/mp3' as well as 'audio/mpeg'.
    • Added some diagnostic logging for album-art extraction
  • Fixed a bug in the formupload handler's template when uploaded files don't have an associated
    title.
  • Wrapped Rakefile tasklib loading in better diagnostics
  • Renamed the 'rdoc' tasklib to 'docs' and moved the manual task to that library to eliminate the
    dependency on 'uv' for non-manual-related tasks.
  • Added coverage to the spec for ThingFish::Request and made #check_body_ios check appended
    resource IOs as well as entity bodies.
  • Added a 'bench' shortcut for 'benchmarks:all' to the benchmark tasklib
  • Fixed a typo in ThingFish::Client API docs.
  • Changed the way recursion works in ThingFish::Request#yield_each_resource -- no longer (needs)
    uses Enumerable::Enumerator, and eliminated newly-appended resources from sometimes being
    yielded back to the block. Thanks to Jeremiah Chase for pairing with me while tracking this
    down.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • thingfish/trunk/Rakefile

    r568 r580  
    8989end 
    9090 
    91 # Load task plugin
     91### Load task librarie
    9292require RAKE_TASKDIR + 'svn.rb' 
    9393require RAKE_TASKDIR + 'verifytask.rb' 
    9494Pathname.glob( RAKE_TASKDIR + '*.rb' ).each do |tasklib| 
    95     trace "Loading task lib #{tasklib}" 
    96     require tasklib 
     95    next if tasklib =~ %r{/(helpers|svn|verifytask)\.rb$} 
     96    begin 
     97        require tasklib 
     98    rescue ScriptError => err 
     99        fail "Task library '%s' failed to load: %s: %s" % 
     100            [ tasklib, err.class.name, err.message ] 
     101        trace "Backtrace: \n  " + err.backtrace.join( "\n  " ) 
     102    rescue => err 
     103        log "Task library '%s' failed to load: %s: %s. Some tasks may not be available." % 
     104            [ tasklib, err.class.name, err.message ] 
     105        trace "Backtrace: \n  " + err.backtrace.join( "\n  " ) 
     106    end 
    97107end 
     108 
    98109 
    99110### Default task 
     
    115126multitask :docs => [ :manual, :coverage, :rdoc ] do 
    116127    log "All documentation built." 
    117 end 
    118  
    119  
    120 ### Task: manual 
    121 require 'misc/rake/lib/manual' 
    122  
    123 directory MANUALOUTPUTDIR.to_s 
    124 directory RDOCDIR.to_s 
    125  
    126 Manual::GenTask.new do |manual| 
    127     manual.metadata.version = PKG_VERSION 
    128     manual.metadata.api_dir = RDOCDIR 
    129     manual.output_dir = MANUALOUTPUTDIR 
    130     manual.base_dir = MANUALDIR 
    131     manual.source_dir = 'src' 
    132 end 
    133  
    134 task :clobber_manual do 
    135     rmtree( MANUALOUTPUTDIR, :verbose => true ) 
    136128end 
    137129 
  • thingfish/trunk/lib/thingfish/client.rb

    r498 r580  
    141141 
    142142    ### Create a new ThingFish client object which will interact with the 
    143     ### server at the specified +endpoint+, which can be either the hostane, 
     143    ### server at the specified +endpoint+, which can be either the hostname, 
    144144    ### the IP address, or the URI of the server. Also set any options 
    145145    ### specified as attributes on the client. 
  • thingfish/trunk/lib/thingfish/request.rb

    r568 r580  
    326326    ### metadata required for the filestore and metastore themselves. 
    327327    def each_body( include_appended=false, &block )  # :yields: body_io, metadata_hash 
    328  
    329         # Build an iterator over entity_bodies and start the traversal of resources 
    330         iter = Enumerable::Enumerator.new( self.entity_bodies, :each ) 
    331         self.yield_each_resource( iter, include_appended, &block ) 
     328        self.yield_each_resource( self.entity_bodies, include_appended, &block ) 
    332329    end 
    333330     
     
    348345    ### Check the body IO objects to ensure they're still open. 
    349346    def check_body_ios 
    350         self.each_body do |body,_| 
     347        self.each_body( true ) do |body,_| 
    351348            if body.closed? 
    352349                self.log.warn "Body IO unexpectedly closed -- reopening a new handle" 
    353350                                 
    354351                # Create a new IO based on what the original type was 
    355                 clone = case body 
    356                    when StringIO 
    357                        StringIO.new( body.string ) 
    358                    else 
    359                        File.open( body.path, 'r' ) 
    360                    end 
     352                clone = nil 
     353                if body.is_a?( StringIO ) 
     354                    clone = StringIO.new( body.string ) 
     355                else 
     356                    clone = File.open( body.path, 'r' ) 
     357                end 
    361358                 
    362                 # Retain the original IO's metadata 
     359                # Retain the original IO's file and appended metadata 
    363360                @entity_bodies[ clone ] = @entity_bodies.delete( body ) if @entity_bodies.key?( body ) 
     361                @metadata[ clone ] = @metadata.delete( body ) if @metadata.key?( body ) 
     362                 
     363                # Splice in the cloned IO into both keys and values of the related resources 
     364                # tree 
    364365                @related_resources[ clone ] = @related_resources.delete( body ) if @related_resources.key?( body ) 
    365                 @metadata[ clone ] = @metadata.delete( body ) if @metadata.key?( body ) 
    366366                @related_resources.each do |_,hash| 
    367367                    hash[ clone ] = hash.delete( body ) if hash.key?( body ) 
    368368                end 
    369369 
    370                 self.log.debug "Body %p (%d) replaced with %p (%d)" % [  
    371                     body, body.object_id, clone, clone.object_id 
    372                 ] 
     370                self.log.debug "Body %p (%d) replaced with %p (%d)" % 
     371                    [ body, body.object_id, clone, clone.object_id ] 
    373372            else 
    374373                body.rewind 
     
    539538    ### toplevel metadata with the resource-specific metadata and pass both to the 
    540539    ### block. 
    541     def yield_each_resource( iterator, include_appended, &block ) 
     540    def yield_each_resource( resources, include_appended, &block ) 
    542541        immutable_metadata = self.get_immutable_metadata 
     542        self.log.debug "Yielding for resources: %p" % [ resources ] 
    543543         
    544544        # Call the block for every resource 
    545         iterator.each do |body, body_metadata| 
     545        resources.keys.each do |body| 
     546            self.log.debug "Resource is: %p" % [ body ] 
     547            body_metadata = resources[ body ] 
     548            appended = self.related_resources[ body ].dup if self.related_resources.key?( body ) 
     549         
    546550            body_metadata[ :format ] ||= DEFAULT_CONTENT_TYPE 
    547551            extracted_metadata = self.metadata[body] || {} 
     
    555559             
    556560            # Recurse if the appended resources should be included 
    557             if include_appended 
    558                 iter = Enumerable::Enumerator.new( self.related_resources[ body ], :each ) 
    559                 self.yield_each_resource( iter, true, &block ) 
     561            if appended && !appended.empty? && include_appended 
     562                self.yield_each_resource( appended, true, &block ) 
    560563            end 
    561564        end 
  • thingfish/trunk/misc/rake/benchmark.rb

    r529 r580  
    9191 
    9292task :benchmarks => [ 'benchmarks:all' ] 
     93task :bench => :benchmarks 
    9394 
    9495 
  • thingfish/trunk/misc/rake/docs.rb

    r550 r580  
    55 
    66require 'rake/rdoctask' 
     7 
     8# Try to require Darkfish for rdoc, but don't mandate it 
     9begin 
     10    require 'rubygems' 
     11    gem 'darkfish-rdoc' 
     12rescue LoadError; end # (ignored) 
     13 
     14begin 
     15    require 'rdoc/generator/darkfish' 
     16rescue LoadError; end # (ignored) 
     17 
     18directory RDOCDIR.to_s 
    719 
    820### Task: rdoc 
     
    1527        '-SHN', 
    1628        '-i', BASEDIR.to_s, 
    17         '-f', 'darkfish', 
    1829        '-m', 'README', 
    1930        '-W', 'http://opensource.laika.com/browser/thingfish/trunk/' 
    2031      ] 
     32 
     33    rdoc.options += [ '-f', 'darkfish' ]  
    2134     
    2235    rdoc.rdoc_files.include 'README' 
     
    2841end 
    2942 
     43 
     44### Task: manual generation 
     45begin 
     46    require 'misc/rake/lib/manual' 
     47 
     48    directory MANUALOUTPUTDIR.to_s 
     49 
     50    Manual::GenTask.new do |manual| 
     51        manual.metadata.version = PKG_VERSION 
     52        manual.metadata.api_dir = RDOCDIR 
     53        manual.output_dir = MANUALOUTPUTDIR 
     54        manual.base_dir = MANUALDIR 
     55        manual.source_dir = 'src' 
     56    end 
     57 
     58    task :clobber_manual do 
     59        rmtree( MANUALOUTPUTDIR, :verbose => true ) 
     60    end 
     61 
     62rescue LoadError => err 
     63    task :no_manual do 
     64        $stderr.puts "Manual-generation tasks not defined: %s" % [ err.message ] 
     65    end 
     66 
     67    task :manual => :no_manual 
     68    task :clobber_manual => :no_manual 
     69end 
     70 
     71 
     72 
  • thingfish/trunk/plugins/thingfish-filestore-filesystem/lib/thingfish/filestore/filesystem.rb

    r548 r580  
    186186        end 
    187187         
     188        self.log.info "Wrote %d bytes." % [ data.length ] 
    188189        self.update_size( path, data.length ) 
    189190        return Digest::MD5.hexdigest( data ) 
     
    214215        end 
    215216 
     217        self.log.info "Wrote %d bytes (buffered)." % [ uploadsize ] 
    216218        self.update_size( path, uploadsize ) 
    217219        return digest.hexdigest 
  • thingfish/trunk/plugins/thingfish-filter-image/lib/thingfish/filter/image.rb

    r496 r580  
    5151    # SVN Id 
    5252    SVNId = %q$Id$ 
     53     
     54    # The default dimensions of thumbnails 
     55    DEFAULT_THUMBNAIL_DIMENSIONS = [ 100, 100 ] 
    5356 
    5457 
     
    117120    ### Set up a new Filter object 
    118121    def initialize( options={} ) # :notnew: 
    119          
    120         # Transform the installed ImageMagick's list of formats into AcceptParams  
    121         # for easy comparison later. 
    122         #   A hash of image formats and their properties. Each key in the returned  
    123         #   hash is the name of a supported image format. Each value is a string in  
    124         #   the form "BRWA". The details are in this table: 
    125         #     B     is "*" if the format has native blob support, and "-" otherwise. 
    126         #     R     is "r" if ×Magick can read the format, and "-" otherwise. 
    127         #     W     is "w" if ×Magick can write the format, and "-" otherwise. 
    128         #     A     is "+" if the format supports multi-image files, and "-" otherwise. 
    129         @supported_formats = {} 
    130         Magick.formats.each do |ext,support| 
    131             operations = MagickOperations.new( ext, support ) 
    132             unless mimetype = MIMETYPE_MAP[ '.' + ext.downcase ] 
    133                 next 
    134             end 
    135              
    136             # Skip some mediatypes that are too generic to be useful 
    137             next if IGNORED_MIMETYPES.include?( mimetype ) 
    138              
    139             self.log.debug "Registering image format %s (%s)" % [ mimetype, operations ] 
    140             @supported_formats[ mimetype ] = operations 
    141         end 
     122        options ||= {} 
     123         
     124        @supported_formats = find_supported_formats() 
    142125        self.log.debug "Registered mimetype mapping for %d of %d support image types" % 
    143126            [ @supported_formats.keys.length, Magick.formats.length ] 
     
    153136            collect {|type, op| ThingFish::AcceptParam.parse(type) } 
    154137         
     138        @thumb_dimensions = options[:thumbnail_dimensions] || DEFAULT_THUMBNAIL_DIMENSIONS 
     139        raise ThingFish::ConfigError, "invalid thumbnail dimensions %p" % [ @thumb_dimensions ] unless 
     140            @thumb_dimensions.is_a?( Array ) && @thumb_dimensions.all? {|dim| dim.is_a?(Fixnum) } 
     141        self.log.debug "Thumbnail dimensions are: %p" % [ @thumb_dimensions ] 
     142         
     143        @thumb_dimensions = options[:thumbnail_dimensions] || DEFAULT_THUMBNAIL_DIMENSIONS 
     144        raise ThingFish::ConfigError, "invalid thumbnail dimensions %p" % [ @thumb_dimensions ] unless 
     145            @thumb_dimensions.is_a?( Array ) && @thumb_dimensions.all? {|dim| dim.is_a?(Fixnum) } 
     146        self.log.debug "Thumbnail dimensions are: %p" % [ @thumb_dimensions ] 
     147         
    155148        super 
    156149    end 
     
    175168                      request.http_method == 'POST' 
    176169 
    177         request.each_body do |body,metadata| 
     170        request.each_body( true ) do |body,metadata| 
     171            next if metadata[:relation] == 'thumbnail' # No thumbnails of thumbnails 
     172 
    178173            if self.accept?( metadata[:format] ) 
    179174 
     
    184179                image = images.first 
    185180                image_attributes = self.extract_metadata( image ) 
    186              
    187181                request.append_metadata_for( body, image_attributes ) 
     182                 
     183                thumbnail, thumb_metadata = self.create_thumbnail( image, metadata[:title] ) 
     184                self.log.debug "Appending a thumbnail as a related resource" 
     185                request.append_related_resource( body, StringIO.new(thumbnail), thumb_metadata ) 
    188186            else 
    189187                self.log.debug "Skipping unhandled file type (%s)" % [metadata[:format]] 
     
    244242          } 
    245243    end 
    246      
    247  
    248  
    249  
     244 
     245 
     246    ######### 
     247    protected 
     248    ######### 
     249 
     250    ### Extract metadata from the given +image+ (a Magick::Image object) and return it in 
     251    ### a Hash. 
     252    def extract_metadata( image ) 
     253        image_attributes = {} 
     254         
     255        image_attributes['image_height']       = image.rows 
     256        image_attributes['image_width']        = image.columns 
     257        image_attributes['image_depth']        = image.depth 
     258        image_attributes['image_density']      = image.density 
     259        image_attributes['image_gamma']        = image.gamma 
     260        image_attributes['image_bounding_box'] = image.bounding_box 
     261         
     262        return image_attributes 
     263    end 
     264     
     265     
     266    ### Create a thumbnail from the given +image+ and return it in a string along with any 
     267    ### associated metadata. 
     268    def create_thumbnail( image, title ) 
     269        self.log.debug "Making thumbnail of max dimensions: [%d X %d]" % @thumb_dimensions 
     270        thumb = image.resize_to_fit( *@thumb_dimensions ) 
     271        imgdata = thumb.to_blob 
     272 
     273        metadata = self.extract_metadata( thumb ) 
     274        metadata.merge!({ 
     275            :format => thumb.mime_type, 
     276            :relation => 'thumbnail', 
     277            :title => "Thumbnail of %s" % [ title || image.inspect ], 
     278            :extent => imgdata.length, 
     279        }) 
     280         
     281        self.log.debug "Made thumbnail for %p" % [ image ] 
     282        return imgdata, metadata 
     283    end 
     284     
     285     
    250286    ####### 
    251287    private 
     
    309345 
    310346 
    311     ######### 
    312     protected 
    313     ######### 
    314  
    315  
    316     ### Extract metadata from the given +image+ (a Magick::Image object) and return it in 
    317     ### a Hash. 
    318     def extract_metadata( image ) 
    319         image_attributes = {} 
    320          
    321         image_attributes['image_height']       = image.rows 
    322         image_attributes['image_width']        = image.columns 
    323         image_attributes['image_depth']        = image.depth 
    324         image_attributes['image_density']      = image.density 
    325         image_attributes['image_gamma']        = image.gamma 
    326         image_attributes['image_bounding_box'] = image.bounding_box 
    327          
    328         return image_attributes 
    329     end 
    330      
    331      
     347    ### Transform the installed ImageMagick's list of formats into AcceptParams  
     348    ### for easy comparison later. 
     349    def find_supported_formats 
     350        formats = {} 
     351 
     352        # A hash of image formats and their properties. Each key in the returned  
     353        # hash is the name of a supported image format. Each value is a string in  
     354        # the form "BRWA". The details are in this table: 
     355        #   B   is "*" if the format has native blob support, and "-" otherwise. 
     356        #   R   is "r" if ×Magick can read the format, and "-" otherwise. 
     357        #   W   is "w" if ×Magick can write the format, and "-" otherwise. 
     358        #   A   is "+" if the format supports multi-image files, and "-" otherwise. 
     359        Magick.formats.each do |ext,support| 
     360            operations = MagickOperations.new( ext, support ) 
     361            unless mimetype = MIMETYPE_MAP[ '.' + ext.downcase ] 
     362                next 
     363            end 
     364             
     365            # Skip some mediatypes that are too generic to be useful 
     366            next if IGNORED_MIMETYPES.include?( mimetype ) 
     367             
     368            self.log.debug "Registering image format %s (%s)" % [ mimetype, operations ] 
     369            formats[ mimetype ] = operations 
     370        end 
     371         
     372        return formats 
     373    end 
     374     
     375 
    332376end # class ThingFish::ImageFilter 
    333377 
  • thingfish/trunk/plugins/thingfish-filter-image/spec/thingfish/filter/image_spec.rb

    r496 r580  
    104104     
    105105     
    106     it "extracts dimension metadata from uploaded image data using RMagick" do 
     106    it "extracts image metadata and thumbnail from uploaded image data using RMagick" do 
    107107        @request.should_receive( :http_method ).at_least( :once ).and_return( 'POST' ) 
    108108         
    109         image = mock( "image object", :null_object => true ) 
     109        # Image metadata 
     110        image = mock( "image object" ) 
    110111        Magick::Image.should_receive( :from_blob ).with( :imagedata ).and_return([ image ]) 
    111112 
    112         @extracted_metadata.each do |key, val| 
    113             image.should_receive( val ).and_return( val.to_s ) 
    114             @extracted_metadata[ key ] = val.to_s 
     113        # Set up the mock to emulate an ImageMagick object and build the expected metadata that'll 
     114        # be extracted by that interface 
     115        expected_metadata = {} 
     116        @extracted_metadata.each do |magick_method, metadata_key| 
     117            image.stub!( metadata_key ).and_return( metadata_key.to_s ) 
     118            expected_metadata[ magick_method ] = metadata_key.to_s 
    115119        end 
    116120         
    117         @request.should_receive( :append_metadata_for ).with( @io, @extracted_metadata ) 
     121        @request.should_receive( :append_metadata_for ).with( @io, expected_metadata ) 
     122 
     123        # Thumbnail 
     124        thumbnail = mock( "thumbnail image object" ) 
     125        image.should_receive( :resize_to_fit ).with( *ThingFish::ImageFilter::DEFAULT_THUMBNAIL_DIMENSIONS ). 
     126            and_return( thumbnail ) 
     127         
     128        thumb_metadata = {} 
     129        @extracted_metadata.each do |magick_method, metadata_key| 
     130            thumb_metadata[ metadata_key ] = metadata_key.to_s 
     131        end 
     132         
     133        image.stub!( :inspect ).and_return( '<inspected image>' ) 
     134        thumbnail.stub!( :mime_type ).and_return( :mimetype ) 
     135        thumb_metadata = { 
     136            :format   => :mimetype, 
     137            :relation => 'thumbnail', 
     138            :title    => "Thumbnail of <inspected image>", 
     139            :extent   => "thumbnail_data".length, 
     140        } 
     141 
     142        # Set up the mock to emulate an ImageMagick object and build the expected metadata that'll 
     143        # be extracted by that interface 
     144        @extracted_metadata.each do |metadata_key, magick_method| 
     145            thumbnail.stub!( magick_method ).and_return( magick_method.to_s ) 
     146            thumb_metadata[ metadata_key ] = magick_method.to_s 
     147        end 
     148         
     149        thumbnail.should_receive( :to_blob ).and_return( "thumbnail_data" ) 
     150        StringIO.should_receive( :new ).with( "thumbnail_data" ).and_return( :thumbio ) 
     151        @request.should_receive( :append_related_resource ).with( @io, :thumbio, thumb_metadata ) 
    118152 
    119153        # Run the request filter 
  • thingfish/trunk/plugins/thingfish-filter-mp3/lib/thingfish/filter/mp3.rb

    r572 r580  
    7676     
    7777    # The Array of types this filter is interested in 
    78     HANDLED_TYPES = [ ThingFish::AcceptParam.parse('audio/mpeg') ] 
     78    HANDLED_TYPES = [ 'audio/mpeg', 'audio/mpg', 'audio/mp3' ]. 
     79        collect {|mimetype| ThingFish::AcceptParam.parse(mimetype) } 
    7980    HANDLED_TYPES.freeze 
    8081 
     
    162163    ### Normalize metadata from the MP3Info object and return it as a hash. 
    163164    def extract_id3_metadata( id3 ) 
     165        self.log.debug "Extracting MP3 metadata" 
     166         
    164167        mp3_metadata = { 
    165168            :mp3_frequency => id3.samplerate, 
     
    203206    ### } 
    204207    def extract_images( id3 ) 
     208        self.log.debug "Extracting embedded images" 
    205209        data = {} 
    206         return data unless id3.hastag2? 
     210         
     211        unless id3.hastag2? 
     212            self.log.debug "...no id3v2 tag, so no embedded images possible." 
     213            return 
     214        end 
     215         
     216        self.log.debug "...id3v2 tag present..." 
    207217         
    208218        if id3.tag2.APIC 
     219            self.log.debug "...extracting APIC (id3v2.3+) image data." 
     220 
    209221            images = [ id3.tag2.APIC ].flatten 
    210222            images.each do |img| 
     
    218230             
    219231        elsif id3.tag2.PIC 
     232            self.log.debug "...extracting PIC (id3v2.2) image data." 
     233 
    220234            images = [ id3.tag2.PIC ].flatten 
    221235            images.each do |img| 
     
    228242                } 
    229243            end 
     244             
     245        else 
     246            self.log.debug "...no known image tag types in tags: %p" % [ id3.tag2.keys.sort ] 
    230247        end 
    231248         
  • thingfish/trunk/plugins/thingfish-handler-formupload/resources/upload.rhtml

    r397 r580  
    22<h2>Uploaded <%= files.length %> file(s)</h2> 
    33<ul> 
    4 <% files.sort_by { |mk, mv| mv[:title] }.each do |file, meta| %> 
     4<% files.sort_by { |mk, mv| mv[:title] || '' }.each do |file, meta| %> 
    55        <li><a href="<%= meta[:uuid] %>" title="<%= meta[:uuid] %>"><%= meta[:title] %></a></li> 
    66<% end %> 
  • thingfish/trunk/spec/thingfish/request_spec.rb

    r568 r580  
    3939    TEMPFILE_PATH = '/var/folders/k7/k7DNYX+ZGSOBod3-pJ-Lhk++0+I/-Tmp-/thingfish.65069.0' 
    4040     
    41     before(:all) do 
     41    before( :all ) do 
    4242        setup_logging( :fatal ) 
    4343    end 
     
    118118    end 
    119119     
     120     
    120121    it "knows when it does not have a multipart/form-data body" do 
    121122        params = @default_params.merge({ 
     
    181182        request.metadata[ upload ].should have(1).member 
    182183        request.metadata[ upload ][ 'relation' ].should == 'thumbnail' 
     184    end 
     185     
     186     
     187    it "requires appended metadata to be associated with a body that's part of the request" do 
     188        params = { 
     189            'HTTP_CONTENT_TYPE'   => 'image/x-bitmap', 
     190            'HTTP_CONTENT_LENGTH' => 18181, 
     191            'HTTP_USER_AGENT'     => 'GarglePants/1.0', 
     192            'REMOTE_ADDR'         => '127.0.0.1', 
     193        } 
     194        upload = StringIO.new( TEST_CONTENT ) 
     195        @mongrel_request.stub!( :params ).and_return( params ) 
     196        @mongrel_request.stub!( :body ).and_return( upload ) 
     197        request = ThingFish::Request.new( @mongrel_request, @config ) 
     198 
     199        mystery_resource = StringIO.new( "mystery content" ) 
     200 
     201        lambda {  
     202            request.each_body do |body, _| 
     203                metadata = { 'relation' => 'thumbnail' } 
     204                request.append_metadata_for( mystery_resource, metadata ) 
     205            end 
     206        }.should raise_error( ThingFish::ResourceError, /cannot append/i ) 
    183207    end 
    184208     
     
    290314        request = ThingFish::Request.new( @mongrel_request, @config ) 
    291315 
    292  
    293316        mystery_resource = StringIO.new( "mystery content" ) 
    294317        generated_resource = StringIO.new( "generated content" ) 
     
    535558        request.is_ajax_request?.should == false 
    536559    end 
    537      
    538  
    539     describe " with cache headers" do 
     560 
     561 
     562    describe "with regular request headers" do 
     563 
     564        before( :all ) do 
     565            setup_logging( :fatal ) 
     566        end 
     567 
     568        after( :all ) do 
     569            reset_logging() 
     570        end 
    540571 
    541572        before( :each ) do 
     
    545576                'SERVER_NAME'   => 'thingfish.laika.com', 
    546577                'SERVER_PORT'   => '3474', 
     578                'REMOTE_ADDR'   => '127.0.0.1', 
    547579                'REQUEST_PATH'  => '/', 
    548580                'QUERY_STRING'  => '', 
     581                'CONTENT_TYPE'  => 'image/jpeg', 
    549582            }) 
    550583            @request_headers = mock( "request headers", :null_object => true ) 
     
    555588            @etag = %{"%s"} % [ TEST_CHECKSUM ] 
    556589            @weak_etag = %{W/"v1.2"} 
     590        end 
     591 
     592 
     593        ### Body IO checks 
     594         
     595        it "can sanity-check the IO objects that contain entity body data for EOF state" do 
     596            io = mock( "Entity body IO" ) 
     597 
     598            @request.instance_variable_set( :@entity_bodies, { io => {} } ) 
     599            @request.instance_variable_set( :@form_metadata, {} ) 
     600             
     601            io.should_receive( :closed? ).and_return( false ) 
     602            io.should_receive( :rewind ) 
     603             
     604            @request.check_body_ios 
     605        end 
     606 
     607 
     608        it "can replace a File IO object that contains entity body data if it becomes closed" do 
     609            io = mock( "Entity body File IO" ) 
     610            clone_io = mock( "Cloned entity body File IO" ) 
     611            @request.instance_variable_set( :@entity_bodies, { io => {} } ) 
     612            @request.instance_variable_set( :@form_metadata, {} ) 
     613             
     614            io.should_receive( :closed? ).and_return( true ) 
     615            io.should_receive( :is_a? ).with( StringIO ).and_return( false ) 
     616            io.should_receive( :path ).and_return( :filepath ) 
     617            File.should_receive( :open ).with( :filepath, 'r' ).and_return( clone_io ) 
     618             
     619            @request.check_body_ios 
     620             
     621            @request.instance_variable_get( :@entity_bodies ).should_not have_key( io ) 
     622            @request.metadata.should_not have_key( io ) 
     623            @request.instance_variable_get( :@entity_bodies ).should have_key( clone_io ) 
     624            @request.metadata.should have_key( clone_io ) 
     625        end 
     626 
     627 
     628        it "can replace a StringIO object that contains entity body data if it becomes closed" do 
     629            io = mock( "Entity body StringIO" ) 
     630            clone_io = mock( "Cloned entity body StringIO" ) 
     631            @request.instance_variable_set( :@entity_bodies, { io => {} } ) 
     632            @request.instance_variable_set( :@form_metadata, {} ) 
     633             
     634            io.should_receive( :closed? ).and_return( true ) 
     635            io.should_receive( :is_a? ).with( StringIO ).and_return( true ) 
     636            io.should_receive( :string ).and_return( :stringdata ) 
     637            StringIO.should_receive( :new ).with( :stringdata ).and_return( clone_io ) 
     638             
     639            # clone_io.should_receive( :closed? ).and_return( false ) 
     640            # clone_io.should_receive( :rewind ) 
     641 
     642            @request.check_body_ios 
    557643        end 
    558644 
     
    672758                and_return([ entity_bodies, form_metadata ]) 
    673759 
     760            thumb_metadata = { 
     761                :relation => 'thumbnail', 
     762                :format   => 'image/jpeg', 
     763                :title    => 'filename1_thumb.jpg', 
     764              } 
     765                 
    674766            yielded_pairs = {} 
     767            @request.each_body do |res, parsed_metadata| 
     768                if res == io1 
     769                    @request.append_related_resource( res, resource1, thumb_metadata ) 
     770                end 
     771            end 
    675772            @request.each_body( true ) do |res, parsed_metadata| 
    676                 if res == io1 
    677                     thumb_metadata = { 
    678                         :relation => 'thumbnail', 
    679                         :format   => 'image/jpeg', 
    680                         :title    => 'filename1_thumb.jpg', 
    681                       } 
    682                     @request.append_related_resource( io1, resource1, thumb_metadata ) 
    683                 end 
    684                      
    685773                yielded_pairs[ res ] = parsed_metadata 
    686774            end 
     
    829917        end 
    830918     
     919 
     920        ### Body IO checks 
     921         
     922        it "can sanity-check multiple IO objects that contain entity body data for EOF state" do 
     923            io1 = mock( "filehandle 1" ) 
     924            sub_io1 = mock( "extracted filehandle 1" ) 
     925            io2 = mock( "filehandle 2" ) 
     926         
     927            parser = mock( "multipart parser", :null_object => true ) 
     928            entity_bodies = { 
     929                io1 => {:title  => "filename1", :extent => 100292}, 
     930                io2 => {:title  => "filename2", :extent => 100234} 
     931              } 
     932            related_resources = { 
     933                io1 => { sub_io1 => {} } 
     934              } 
     935         
     936            @request.instance_variable_set( :@entity_bodies, { io1 => {}, io2 => {} } ) 
     937            @request.instance_variable_set( :@form_metadata, {} ) 
     938            @request.instance_variable_set( :@related_resources, related_resources ) 
     939             
     940            io1.should_receive( :closed? ).and_return( false ) 
     941            io1.should_receive( :rewind ) 
     942         
     943            sub_io1.should_receive( :closed? ).and_return( false ) 
     944            sub_io1.should_receive( :rewind ) 
     945         
     946            io2.should_receive( :closed? ).and_return( false ) 
     947            io2.should_receive( :rewind ) 
     948             
     949            @request.check_body_ios 
     950        end 
     951 
     952 
     953        it "can replace an appended File IO object if it becomes closed" do 
     954            io1 = mock( "filehandle 1" ) 
     955            sub_io1 = mock( "extracted filehandle 1" ) 
     956            clone_sub_io1 = mock( "extracted filehandle 1" ) 
     957            io2 = mock( "filehandle 2" ) 
     958         
     959            parser = mock( "multipart parser", :null_object => true ) 
     960            entity_bodies = { 
     961                io1 => {:title  => "filename1", :extent => 100292}, 
     962                io2 => {:title  => "filename2", :extent => 100234} 
     963              } 
     964            related_resources = { 
     965                io1 => { sub_io1 => {} } 
     966              } 
     967         
     968            @request.instance_variable_set( :@entity_bodies, { io1 => {}, io2 => {} } ) 
     969            @request.instance_variable_set( :@form_metadata, {} ) 
     970            @request.instance_variable_set( :@related_resources, related_resources ) 
     971             
     972            io1.should_receive( :closed? ).and_return( false ) 
     973            io1.should_receive( :rewind ) 
     974         
     975            sub_io1.should_receive( :closed? ).and_return( true ) 
     976            sub_io1.should_receive( :is_a? ).with( StringIO ).and_return( false ) 
     977            sub_io1.should_receive( :path ).and_return( :filepath ) 
     978            File.should_receive( :open ).with( :filepath, 'r' ).and_return( clone_sub_io1 ) 
     979             
     980            io2.should_receive( :closed? ).and_return( false ) 
     981            io2.should_receive( :rewind ) 
     982             
     983            @request.check_body_ios 
     984 
     985            @request.related_resources[ io1 ].keys.should == [clone_sub_io1] 
     986            @request.metadata.should_not have_key( sub_io1 ) 
     987            @request.metadata.should have_key( clone_sub_io1 ) 
     988        end 
     989         
     990         
     991        it "can replace a StringIO object that contains entity body data if it becomes closed" do 
     992            io1 = mock( "filehandle 1" ) 
     993            sub_io1 = mock( "extracted filehandle 1" ) 
     994            clone_sub_io1 = mock( "extracted filehandle 1" ) 
     995            io2 = mock( "filehandle 2" ) 
     996         
     997            parser = mock( "multipart parser", :null_object => true ) 
     998            entity_bodies = { 
     999                io1 => {:title  => "filename1", :extent => 100292}, 
     1000                io2 => {:title  => "filename2", :extent => 100234} 
     1001              } 
     1002            related_resources = { 
     1003                io1 => { sub_io1 => {} } 
     1004              } 
     1005         
     1006            @request.instance_variable_set( :@entity_bodies, { io1 => {}, io2 => {} } ) 
     1007            @request.instance_variable_set( :@form_metadata, {} ) 
     1008            @request.instance_variable_set( :@related_resources, related_resources ) 
     1009             
     1010            io1.should_receive( :closed? ).and_return( false ) 
     1011            io1.should_receive( :rewind ) 
     1012         
     1013            sub_io1.should_receive( :closed? ).and_return( true ) 
     1014            sub_io1.should_receive( :is_a? ).with( StringIO ).and_return( true ) 
     1015            sub_io1.should_receive( :string ).and_return( :datastring ) 
     1016            StringIO.should_receive( :new ).with( :datastring ).and_return( clone_sub_io1 ) 
     1017             
     1018            io2.should_receive( :closed? ).and_return( false ) 
     1019            io2.should_receive( :rewind ) 
     1020             
     1021            @request.check_body_ios 
     1022 
     1023            @request.related_resources[ io1 ].keys.should == [clone_sub_io1] 
     1024            @request.metadata.should_not have_key( sub_io1 ) 
     1025            @request.metadata.should have_key( clone_sub_io1 ) 
     1026        end 
     1027 
    8311028    end 
    8321029