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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
|
# -*- coding: utf-8 -*-
# 画像のURLを受け取って、Gtk::Pixbufを返す
miquire :core, 'serialthread', 'skin'
miquire :mui, 'web_image_loader_image_cache'
miquire :lib, 'addressable/uri'
require 'net/http'
require 'uri'
require 'thread'
require 'fileutils'
module Gdk::WebImageLoader
extend Gdk::WebImageLoader
WebImageThread = SerialThreadGroup.new
WebImageThread.max_threads = 16
# URLから画像をダウンロードして、その内容を持ったGdk::Pixbufのインスタンスを返す
# ==== Args
# [url] 画像のURL
# [rect] 画像のサイズ(Gdk::Rectangle) または幅(px)
# [height] 画像の高さ(px)
# [&load_callback]
# 画像のダウンロードで処理がブロッキングされるような場合、ブロックが指定されていれば
# このメソッドはとりあえずloading中の画像のPixbufを返し、ロードが完了したらブロックを呼び出す
# ==== Return
# Pixbuf
def pixbuf(url, rect, height = nil, &load_callback)
url = Plugin.filtering(:web_image_loader_url_filter, url.freeze)[0].freeze
rect = Gdk::Rectangle.new(0, 0, rect, height) if height
pixbuf = ImageCache::Pixbuf.load(url, rect)
return pixbuf if pixbuf
if(is_local_path?(url))
url = File.expand_path(url)
if(FileTest.exist?(url))
GdkPixbuf::Pixbuf.new(file: url, width: rect.width, height: rect.height)
else
notfound_pixbuf(rect) end
else
via_internet(url, rect, &load_callback) end
rescue Gdk::PixbufError
notfound_pixbuf(rect)
rescue => e
if into_debug_mode(e)
raise e
else
notfound_pixbuf(rect) end end
# _url_ が指している画像を任意のサイズにリサイズして、その画像のパスを返す。
# このメソッドは画像のダウンロードが発生すると処理をブロッキングする。
# 取得に失敗した場合は nil を返す。
# ==== Args
# [url] 画像のURL
# [width] 幅(px)
# [height] 高さ(px)
# ==== Return
# 画像のパス
def local_path(url, width = 48, height = width)
url.freeze
ext = (File.extname(url).split("?", 2)[0] or File.extname(url))
filename = File.expand_path(File.join(Environment::TMPDIR, Digest::MD5.hexdigest(url + "#{width}x#{height}") + ext + '.png'))
pb = pixbuf(url, width, height)
if(pb)
pb.save(filename, 'png') if not FileTest.exist?(filename)
local_path_files_add(filename)
filename end end
# urlが指している画像のデータを返す。
# ==== Args
# [url] 画像のURL
# ==== Return
# キャッシュがあればロード後のデータを即座に返す。
# ブロックが指定されれば、キャッシュがない時は :wait を返して、ロードが完了したらブロックを呼び出す。
# ブロックが指定されなければ、ロード完了まで待って、ロードが完了したらそのデータを返す。
def get_raw_data(url, &load_callback) # :yield: raw, exception, url
url.freeze
raw = ImageCache::Raw.load(url)
if raw and not raw.empty?
raw
else
exception = nil
if load_callback
WebImageThread.new{
get_raw_data_load_proc(url, &load_callback) }
:wait
else
get_raw_data_load_proc(url, &load_callback) end end
rescue Gdk::PixbufError
nil end
# get_raw_dataの内部関数。
# HTTPコネクションを張り、 _url_ をダウンロードしてjpegとかpngとかの情報をそのまま返す。
def get_raw_data_load_proc(url, &load_callback)
ImageCache.synchronize(url) {
forerunner_result = ImageCache::Raw.load(url)
if(forerunner_result)
raw = forerunner_result
if load_callback
load_callback.call(*[forerunner_result, nil, url][0..load_callback.arity])
forerunner_result
else
forerunner_result end
else
no_mainthread
begin
res = get_icon_via_http(url)
if(res.is_a?(Net::HTTPResponse)) and (res.code == '200')
raw = res.body.to_s
else
exception = true end
rescue Timeout::Error, StandardError => e
exception = e end
ImageCache::Raw.save(url, raw)
if load_callback
load_callback.call(*[raw, exception, url][0..load_callback.arity])
raw
else
raw end end } end
# get_raw_dataのdeferred版
def get_raw_data_d(url)
url.freeze
promise = Deferred.new true
Thread.new {
result = get_raw_data(url){ |raw, e|
begin
if e
promise.fail(e)
elsif raw and not raw.empty?
promise.call(raw)
else
promise.fail(raw) end
rescue Exception => e
promise.fail(e) end }
if result
if :wait != result
promise.call(result) end
else
promise.fail(result) end }
promise end
# _url_ が、インターネット上のリソースを指しているか、ローカルのファイルを指しているかを返す
# ==== Args
# [url] ファイルのパス又はURL
# ==== Return
# ローカルのファイルならtrue
def is_local_path?(url)
not url.start_with?('http') end
# ロード中の画像のPixbufを返す
# ==== Args
# [rect] サイズ(Gtk::Rectangle) 又は幅(px)
# [height] 高さ
# ==== Return
# Pixbuf
def loading_pixbuf(rect, height = nil)
if height
_loading_pixbuf(rect, height)
else
_loading_pixbuf(rect.width, rect.height) end end
def _loading_pixbuf(width, height)
GdkPixbuf::Pixbuf.new(file: File.expand_path(Skin.get("loading.png")), width: width, height: height).freeze end
memoize :_loading_pixbuf
# 画像が見つからない場合のPixbufを返す
# ==== Args
# [rect] サイズ(Gtk::Rectangle) 又は幅(px)
# [height] 高さ
# ==== Return
# Pixbuf
def notfound_pixbuf(rect, height = nil)
if height
_notfound_pixbuf(rect, height)
else
_notfound_pixbuf(rect.width, rect.height) end end
def _notfound_pixbuf(width, height)
GdkPixbuf::Pixbuf.new(file: File.expand_path(Skin.get("notfound.png")), width: width, height: height).freeze
end
memoize :_notfound_pixbuf
# _src_ が _rect_ にアスペクト比を維持した状態で内接するように縮小した場合のサイズを返す
# ==== Args
# [src] 元の寸法(Gtk::Rectangle)
# [dst] 収めたい枠の寸法(Gtk::Rectangle)
# ==== Return
# Pixbuf
def calc_fitclop(src, dst)
if (dst.width * src.height) > (dst.height * src.width)
return src.width * dst.height / src.height, dst.height
else
return dst.width, src.height * dst.width / src.width end end
private
# urlが指している画像を引っ張ってきてPixbufを返す。
# 画像をダウンロードする場合は、読み込み中の画像を返して、ロードが終わったらブロックを実行する
# ==== Args
# [url] 画像のURL
# [rect] 画像のサイズ(Gdk::Rectangle)
# [&load_callback] ロードが終わったら実行されるブロック
# ==== Return
# ロード中のPixbufか、キャッシュがあればロード後のPixbufを即座に返す
# ブロックが指定されなければ、ロード完了まで待って、ロードが完了したらそのPixbufを返す
def via_internet(url, rect, &load_callback) # :yield: pixbuf, exception, url
url.freeze
if block_given?
raw = get_raw_data(url){ |raw, exception|
pixbuf = notfound_pixbuf(rect)
begin
pixbuf = ImageCache::Pixbuf.save(url, rect, inmemory2pixbuf(raw, rect, true)) if raw
rescue Gdk::PixbufError => e
exception = e
end
Delayer.new{ load_callback.call(pixbuf, exception, url) } }
if raw.is_a?(String)
ImageCache::Pixbuf.save(url, rect, inmemory2pixbuf(raw, rect))
else
loading_pixbuf(rect) end
else
raw = get_raw_data(url)
if raw
ImageCache::Pixbuf.save(url, rect, inmemory2pixbuf(raw, rect))
else
notfound_pixbuf(rect) end end
rescue Gdk::PixbufError
notfound_pixbuf(rect) end
# メモリ上の画像データをPixbufにロードする
# ==== Args
# [image_data] メモリ上の画像データ
# [rect] サイズ(Gdk::Rectangle)
# [raise_exception] 真PixbufError例外を投げる(default: false)
# ==== Exceptions
# Gdk::PixbufError例外が発生したら、notfound_pixbufを返します。
# ただし、 _raise_exception_ が真なら例外を投げます。
# ==== Return
# Pixbuf
def inmemory2pixbuf(image_data, rect, raise_exception = false)
rect = rect.dup
loader = Gdk::PixbufLoader.new
# loader.set_size(rect.width, rect.height) if rect
loader.write image_data
loader.close
pb = loader.pixbuf
pb.scale(*calc_fitclop(pb, rect))
rescue Gdk::PixbufError => e
if raise_exception
raise e
else
notfound_pixbuf(rect) end end
def http(host, port, scheme)
result = nil
atomic{
@http_pool = Hash.new{|h, k|h[k] = Hash.new{|_h, _k|_h[_k] = {} } } if not defined? @http_pool
if not @http_pool[host][port][scheme]
pool = []
@http_pool[host][port][scheme] = Queue.new
4.times { |index|
http = case scheme
when 'http'.freeze
Net::HTTP.new(host, port || 80)
when 'https'.freeze
Net::HTTP.new(host, port || 443).tap{|_h|
_h.use_ssl = true } end
http.open_timeout=5
http.read_timeout=30
pool << http
@http_pool[host][port][scheme].push(pool) } end }
pool = @http_pool[host][port][scheme].pop
http = pool.pop
result = yield(http)
ensure
pool.push(http) if defined? http
@http_pool[host][port][scheme].push(pool) if defined? pool
result
end
def get_icon_via_http(url)
uri = Addressable::URI.parse(url)
request = Net::HTTP::Get.new(uri.request_uri)
request['Connection'] = 'Keep-Alive'
http(uri.host, uri.port, uri.scheme) do |http|
begin
http.request(request)
rescue EOFError => e
http.finish
http.start
notice "open connection for #{uri.host}"
http.request(request) end end end
def local_path_files_add(path)
atomic{
if not defined?(@local_path_files)
@local_path_files = Set.new
at_exit{ FileUtils.rm(@local_path_files.to_a) } end }
@local_path_files << path
end
end
|