Class: Utopia::Static::LocalFile

Inherits:
Object
  • Object
show all
Defined in:
lib/utopia/static/local_file.rb

Overview

Represents a local file on disk which can be served directly, or passed upstream to sendfile.

Constant Summary collapse

CONTENT_LENGTH =
Rack::CONTENT_LENGTH
CONTENT_RANGE =
"Content-Range".freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root, path) ⇒ LocalFile

Returns a new instance of LocalFile.



14
15
16
17
18
19
20
# File 'lib/utopia/static/local_file.rb', line 14

def initialize(root, path)
  @root = root
  @path = path
  @etag = Digest::SHA1.hexdigest("#{File.size(full_path)}#{mtime_date}")
  
  @range = nil
end

Instance Attribute Details

#etagObject (readonly)

Returns the value of attribute etag.



24
25
26
# File 'lib/utopia/static/local_file.rb', line 24

def etag
  @etag
end

#pathObject (readonly)

Returns the value of attribute path.



23
24
25
# File 'lib/utopia/static/local_file.rb', line 23

def path
  @path
end

#rangeObject (readonly)

Returns the value of attribute range.



25
26
27
# File 'lib/utopia/static/local_file.rb', line 25

def range
  @range
end

#rootObject (readonly)

Returns the value of attribute root.



22
23
24
# File 'lib/utopia/static/local_file.rb', line 22

def root
  @root
end

Instance Method Details

#bytesizeObject Also known as: size



40
41
42
# File 'lib/utopia/static/local_file.rb', line 40

def bytesize
  File.size(full_path)
end

#eachObject



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/utopia/static/local_file.rb', line 51

def each
  File.open(full_path, "rb") do |file|
    file.seek(@range.begin)
    remaining = @range.end - @range.begin+1
    
    while remaining > 0
      break unless part = file.read([8192, remaining].min)
      
      remaining -= part.length
      
      yield part
    end
  end
end

#empty?Boolean

This reflects whether calling each would yield anything.

Returns:

  • (Boolean)


45
46
47
# File 'lib/utopia/static/local_file.rb', line 45

def empty?
  bytesize == 0
end

#full_pathObject



32
33
34
# File 'lib/utopia/static/local_file.rb', line 32

def full_path
  File.join(@root, @path.components)
end

#modified?(env) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/utopia/static/local_file.rb', line 66

def modified?(env)
  if modified_since = env["HTTP_IF_MODIFIED_SINCE"]
    return false if File.mtime(full_path) <= Time.parse(modified_since)
  end
  
  if etags = env["HTTP_IF_NONE_MATCH"]
    etags = etags.split(/\s*,\s*/)
    return false if etags.include?(etag) || etags.include?("*")
  end
  
  return true
end

#mtime_dateObject



36
37
38
# File 'lib/utopia/static/local_file.rb', line 36

def mtime_date
  File.mtime(full_path).httpdate
end

#serve(env, response_headers) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/utopia/static/local_file.rb', line 82

def serve(env, response_headers)
  ranges = Rack::Utils.get_byte_ranges(env["HTTP_RANGE"], size)
  response = [200, response_headers, self]
  
  # puts "Requesting ranges: #{ranges.inspect} (#{size})"
  
  if ranges == nil or ranges.size != 1
    # No ranges, or multiple ranges (which we don't support).
    # TODO: Support multiple byte-ranges, for now just send entire file:
    response[0] = 200
    response[1][CONTENT_LENGTH] = size.to_s
    @range = 0...size
  else
    # Partial content:
    @range = ranges[0]
    partial_size = @range.size
    
    response[0] = 206
    response[1][CONTENT_LENGTH] = partial_size.to_s
    response[1][CONTENT_RANGE] = "bytes #{@range.min}-#{@range.max}/#{size}"
  end
  
  return response
end

#to_pathObject

Fit in with Rack::Sendfile



28
29
30
# File 'lib/utopia/static/local_file.rb', line 28

def to_path
  full_path
end