Claude Code fixes. StandardRB
This commit is contained in:
119
lib/moviehash.rb
119
lib/moviehash.rb
@@ -6,12 +6,35 @@ require "uri"
|
|||||||
|
|
||||||
module Moviehash
|
module Moviehash
|
||||||
class Error < StandardError; end
|
class Error < StandardError; end
|
||||||
|
|
||||||
class FileNotFoundError < Error; end
|
class FileNotFoundError < Error; end
|
||||||
|
|
||||||
class NetworkError < Error; end
|
class NetworkError < Error; end
|
||||||
|
|
||||||
CHUNK_SIZE = 64 * 1024 # in bytes
|
class InvalidInputError < Error; end
|
||||||
|
|
||||||
|
DEFAULT_CHUNK_SIZE = 64 * 1024 # in bytes
|
||||||
|
DEFAULT_TIMEOUT = 30 # seconds
|
||||||
|
HASH_MASK = 0xffffffffffffffff
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_writer :chunk_size, :timeout
|
||||||
|
|
||||||
|
def chunk_size
|
||||||
|
@chunk_size || DEFAULT_CHUNK_SIZE
|
||||||
|
end
|
||||||
|
|
||||||
|
def timeout
|
||||||
|
@timeout || DEFAULT_TIMEOUT
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure
|
||||||
|
yield self if block_given?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.compute_hash(url)
|
def self.compute_hash(url)
|
||||||
|
validate_input(url)
|
||||||
data = url.start_with?("http") ? data_from_url(url) : data_from_file(url)
|
data = url.start_with?("http") ? data_from_url(url) : data_from_file(url)
|
||||||
|
|
||||||
hash = data[:filesize]
|
hash = data[:filesize]
|
||||||
@@ -23,73 +46,93 @@ module Moviehash
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.data_from_file(path)
|
def self.data_from_file(path)
|
||||||
filesize = File.size(path)
|
raise FileNotFoundError, "File not found: #{path}" unless File.exist?(path)
|
||||||
|
raise FileNotFoundError, "Path is a directory: #{path}" if File.directory?(path)
|
||||||
|
|
||||||
data = { filesize: filesize, chunks: [] }
|
begin
|
||||||
|
filesize = File.size(path)
|
||||||
|
data = {filesize: filesize, chunks: []}
|
||||||
|
|
||||||
File.open(path, "rb") do |f|
|
File.open(path, "rb") do |f|
|
||||||
data[:chunks] << f.read(CHUNK_SIZE)
|
data[:chunks] << f.read(chunk_size)
|
||||||
f.seek([0, filesize - CHUNK_SIZE].max, IO::SEEK_SET)
|
f.seek([0, filesize - chunk_size].max, IO::SEEK_SET)
|
||||||
data[:chunks] << f.read(CHUNK_SIZE)
|
data[:chunks] << f.read(chunk_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
data
|
data
|
||||||
|
rescue Errno::EACCES
|
||||||
|
raise FileNotFoundError, "Permission denied: #{path}"
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
raise FileNotFoundError, "File not found: #{path}"
|
||||||
|
rescue => e
|
||||||
|
raise Error, "Failed to read file #{path}: #{e.message}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.data_from_url(url)
|
def self.data_from_url(url)
|
||||||
uri = URI(url)
|
uri = URI(url)
|
||||||
|
raise NetworkError, "Invalid URL scheme: #{uri.scheme}" unless %w[http https].include?(uri.scheme)
|
||||||
|
|
||||||
http = Net::HTTP.new(uri.host, uri.port)
|
http = Net::HTTP.new(uri.host, uri.port)
|
||||||
http.use_ssl = (uri.scheme == "https")
|
http.use_ssl = (uri.scheme == "https")
|
||||||
|
http.read_timeout = timeout
|
||||||
|
http.open_timeout = timeout
|
||||||
|
|
||||||
# Get the file size
|
# Get the file size
|
||||||
response = http.request_head(uri.path)
|
response = http.request_head(uri.path)
|
||||||
filesize = response["content-length"].to_i
|
raise NetworkError, "HTTP #{response.code}: #{response.message}" unless response.code == "200"
|
||||||
|
|
||||||
data = { filesize: filesize, chunks: [] }
|
filesize = response["content-length"]&.to_i
|
||||||
|
raise NetworkError, "Server did not provide content-length header" unless filesize && filesize > 0
|
||||||
|
|
||||||
|
data = {filesize: filesize, chunks: []}
|
||||||
|
|
||||||
# Process the beginning of the file
|
# Process the beginning of the file
|
||||||
response = http.get(uri.path, { "Range" => "bytes=0-#{CHUNK_SIZE - 1}" })
|
response = http.get(uri.path, {"Range" => "bytes=0-#{chunk_size - 1}"})
|
||||||
|
raise NetworkError, "Failed to fetch beginning chunk: HTTP #{response.code}" unless response.code.start_with?("2")
|
||||||
data[:chunks] << response.body
|
data[:chunks] << response.body
|
||||||
|
|
||||||
# Process the end of the file
|
# Process the end of the file
|
||||||
start_byte = [0, filesize - CHUNK_SIZE].max
|
start_byte = [0, filesize - chunk_size].max
|
||||||
response = http.get(uri.path, { "Range" => "bytes=#{start_byte}-#{filesize - 1}" })
|
response = http.get(uri.path, {"Range" => "bytes=#{start_byte}-#{filesize - 1}"})
|
||||||
|
raise NetworkError, "Failed to fetch ending chunk: HTTP #{response.code}" unless response.code.start_with?("2")
|
||||||
data[:chunks] << response.body
|
data[:chunks] << response.body
|
||||||
|
|
||||||
data
|
data
|
||||||
|
rescue URI::InvalidURIError => e
|
||||||
|
raise NetworkError, "Invalid URL: #{e.message}"
|
||||||
|
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
||||||
|
raise NetworkError, "Request timeout: #{e.message}"
|
||||||
|
rescue SocketError => e
|
||||||
|
raise NetworkError, "Network error: #{e.message}"
|
||||||
|
rescue => e
|
||||||
|
raise NetworkError, "Failed to fetch from URL: #{e.message}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.process_chunk(chunk, hash)
|
def self.process_chunk(chunk, hash)
|
||||||
|
return hash unless chunk
|
||||||
|
|
||||||
chunk.unpack("Q*").each do |n|
|
chunk.unpack("Q*").each do |n|
|
||||||
hash = hash + n & 0xffffffffffffffff
|
hash = hash + n & HASH_MASK
|
||||||
end
|
end
|
||||||
|
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
private
|
||||||
def self.old_compute_hash(path)
|
|
||||||
filesize = File.size(path)
|
def self.validate_input(input)
|
||||||
hash = filesize
|
raise InvalidInputError, "Input cannot be nil" if input.nil?
|
||||||
|
raise InvalidInputError, "Input cannot be empty" if input.empty?
|
||||||
format("%016x", hash)
|
raise InvalidInputError, "Input must be a string" unless input.is_a?(String)
|
||||||
|
|
||||||
# Read 64 kbytes, divide up into 64 bits and add each
|
if input.start_with?("http")
|
||||||
# to hash. Do for beginning and end of file.
|
begin
|
||||||
File.open(path, "rb") do |f|
|
uri = URI(input)
|
||||||
# Q = unsigned long long = 64 bit
|
raise InvalidInputError, "Invalid URL: missing host" unless uri.host
|
||||||
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
rescue URI::InvalidURIError => e
|
||||||
hash = hash + n & 0xffffffffffffffff # to remain as 64 bit number
|
raise InvalidInputError, "Invalid URL: #{e.message}"
|
||||||
end
|
end
|
||||||
format("%016x", hash)
|
end
|
||||||
f.seek([0, filesize - CHUNK_SIZE].max, IO::SEEK_SET)
|
end
|
||||||
|
|
||||||
# And again for the end of the file
|
|
||||||
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
|
||||||
hash = hash + n & 0xffffffffffffffff
|
|
||||||
end
|
|
||||||
format("%016x", hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
format("%016x", hash)
|
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user