File: file.rb

package info (click to toggle)
ruby-protocol-http 0.55.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 840 kB
  • sloc: ruby: 6,904; makefile: 4
file content (147 lines) | stat: -rw-r--r-- 3,670 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.

require_relative "readable"

module Protocol
	module HTTP
		module Body
			# A body which reads from a file.
			class File < Readable
				# The default block size.
				BLOCK_SIZE = 64*1024
				
				# The default mode for opening files.
				MODE = ::File::RDONLY | ::File::BINARY
				
				# Open a file at the given path.
				#
				# @parameter path [String] the path to the file.
				def self.open(path, *arguments, **options)
					self.new(::File.open(path, MODE), *arguments, **options)
				end
				
				# Initialize the file body with the given file.
				#
				# @parameter file [::File] the file to read from.
				# @parameter range [Range] the range of bytes to read from the file.
				# @parameter size [Integer] the size of the file, if known.
				# @parameter block_size [Integer] the block size to use when reading from the file.
				def initialize(file, range = nil, size: file.size, block_size: BLOCK_SIZE)
					@file = file
					@range = range
					
					@block_size = block_size
					
					if range
						@file.seek(range.min)
						@offset = range.min
						@length = @remaining = range.size
					else
						@file.seek(0)
						@offset = 0
						@length = @remaining = size
					end
				end
				
				# Close the file.
				#
				# @parameter error [Exception | Nil] the error that caused the file to be closed.
				def close(error = nil)
					@file.close
					@remaining = 0
					
					super
				end
				
				# @attribute [::File] file the file to read from.
				attr :file
				
				# @attribute [Integer] the offset to read from.
				attr :offset
				
				# @attribute [Integer] the number of bytes to read.
				attr :length
				
				# @returns [Boolean] whether more data should be read.
				def empty?
					@remaining == 0
				end
				
				# @returns [Boolean] whether the body is ready to be read, always true for files.
				def ready?
					true
				end
				
				# Returns a copy of the body, by duplicating the file descriptor, including the same range if specified.
				#
				# @returns [File] the duplicated body.
				def buffered
					self.class.new(@file.dup, @range, block_size: @block_size)
				end
				
				# Rewind the file to the beginning of the range.
				def rewind
					@file.seek(@offset)
					@remaining = @length
				end
				
				# @returns [Boolean] whether the body is rewindable, generally always true for seekable files.
				def rewindable?
					true
				end
				
				# Read the next chunk of data from the file.
				#
				# @returns [String | Nil] the next chunk of data, or nil if the file is fully read.
				def read
					if @remaining > 0
						amount = [@remaining, @block_size].min
						
						if chunk = @file.read(amount)
							@remaining -= chunk.bytesize
							
							return chunk
						end
					end
				end
				
				# def stream?
				# 	true
				# end
				
				# def call(stream)
				# 	IO.copy_stream(@file, stream, @remaining)
				# ensure
				# 	stream.close
				# end
				
				# Read all the remaining data from the file and return it as a single string.
				#
				# @returns [String] the remaining data.
				def join
					return "" if @remaining == 0
					
					buffer = @file.read(@remaining)
					
					@remaining = 0
					
					return buffer
				end
				
				# Inspect the file body.
				#
				# @returns [String] a string representation of the file body.
				def inspect
					if @offset > 0
						"#<#{self.class} #{@file.inspect} +#{@offset}, #{@remaining} bytes remaining>"
					else
						"#<#{self.class} #{@file.inspect}, #{@remaining} bytes remaining>"
					end
				end
			end
		end
	end
end