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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
# frozen_string_literal: true
require "mini_mime/version"
require "thread"
module MiniMime
def self.lookup_by_filename(filename)
Db.lookup_by_filename(filename)
end
def self.lookup_by_extension(extension)
Db.lookup_by_extension(extension)
end
def self.lookup_by_content_type(mime)
Db.lookup_by_content_type(mime)
end
module Configuration
class << self
attr_accessor :ext_db_path
attr_accessor :content_type_db_path
end
self.ext_db_path = File.expand_path("../db/ext_mime.db", __FILE__)
self.content_type_db_path = File.expand_path("../db/content_type_mime.db", __FILE__)
end
class Info
BINARY_ENCODINGS = %w(base64 8bit)
attr_accessor :extension, :content_type, :encoding
def initialize(buffer)
@extension, @content_type, @encoding = buffer.split(/\s+/).map!(&:freeze)
end
def [](idx)
if idx == 0
@extension
elsif idx == 1
@content_type
elsif idx == 2
@encoding
end
end
def binary?
BINARY_ENCODINGS.include?(encoding)
end
end
class Db
def self.lookup_by_filename(filename)
extension = File.extname(filename)
return if extension.empty?
extension = extension[1..-1]
lookup_by_extension(extension)
end
def self.lookup_by_extension(extension)
@db ||= new
@db.lookup_by_extension(extension) ||
@db.lookup_by_extension(extension.downcase)
end
def self.lookup_by_content_type(content_type)
@db ||= new
@db.lookup_by_content_type(content_type)
end
class Cache
def initialize(size)
@size = size
@hash = {}
end
def []=(key, val)
rval = @hash[key] = val
@hash.shift if @hash.length > @size
rval
end
def fetch(key, &blk)
@hash.fetch(key, &blk)
end
end
if ::File.method_defined?(:pread)
PReadFile = ::File
else
# For Windows support
class PReadFile
def initialize(filename)
@mutex = Mutex.new
# We must open the file in binary mode
# otherwise Ruby's automatic line terminator
# translation will skew the row size
@file = ::File.open(filename, 'rb')
end
def readline(*args)
@file.readline(*args)
end
def pread(size, offset)
@mutex.synchronize do
@file.seek(offset, IO::SEEK_SET)
@file.read(size)
end
end
end
end
class RandomAccessDb
MAX_CACHED = 100
def initialize(path, sort_order)
@path = path
@file = PReadFile.new(@path)
@row_length = @file.readline("\n").length
@file_length = File.size(@path)
@rows = @file_length / @row_length
@hit_cache = Cache.new(MAX_CACHED)
@miss_cache = Cache.new(MAX_CACHED)
@sort_order = sort_order
end
def lookup(val)
@hit_cache.fetch(val) do
@miss_cache.fetch(val) do
data = lookup_uncached(val)
if data
@hit_cache[val] = data
else
@miss_cache[val] = nil
end
data
end
end
end
# lifted from marcandre/backports
def lookup_uncached(val)
from = 0
to = @rows - 1
result = nil
while from <= to do
midpoint = from + (to - from).div(2)
current = resolve(midpoint)
data = current[@sort_order]
if data > val
to = midpoint - 1
elsif data < val
from = midpoint + 1
else
result = current
break
end
end
result
end
def resolve(row)
Info.new(@file.pread(@row_length, row * @row_length).force_encoding(Encoding::UTF_8))
end
end
def initialize
@ext_db = RandomAccessDb.new(Configuration.ext_db_path, 0)
@content_type_db = RandomAccessDb.new(Configuration.content_type_db_path, 1)
end
def lookup_by_extension(extension)
@ext_db.lookup(extension)
end
def lookup_by_content_type(content_type)
@content_type_db.lookup(content_type)
end
end
end
|