-
-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathfile.rb
More file actions
147 lines (119 loc) · 3.58 KB
/
Copy pathfile.rb
File metadata and controls
147 lines (119 loc) · 3.58 KB
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