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
|
# -*- coding: utf-8 -*-
require 'uri'
require 'timeout'
=begin rdoc
URL短縮・展開のためのクラス。これを継承したクラスを作れば短縮URL機能として利用されるようになる
=end
class MessageConverters
class << self
ExpandExpire = Class.new(TimeoutError)
attr_hash_accessor :expand_by_cache, :shrink_by_cache
# _shrinked_ を展開したら _expanded_ になるということをキャッシュに登録する。
# _shrinked_ を返す。
def cache(shrinked, expanded)
expand_by_cache[shrinked] = expanded.freeze
shrink_by_cache[expanded] = shrinked.freeze end
# サブクラスから呼び出す。そのクラスをURLの短縮/展開のためのクラスとして登録する。
def regist
converter = self.new
plugin = Plugin.create(converter.plugin_name)
[:shrink, :expand].each{ |convert|
plugin.add_event_filter("#{convert}_url"){ |url, &cont|
lock(url) do
cached = if convert == :shrink then shrink_by_cache[url] else expand_by_cache[url] end
cont.call([cached]) if cached
converted = converter.__send__("#{convert}_url", url)
if(converted)
if convert == :shrink
cache(converted, url)
else
cache(url, converted) end
cont.call([converted])
else
[url] end end } }
plugin.add_event_filter(:is_expanded){ |url, &cont|
if(converter.shrinked_url?(url))
cont.call([false])
else
[url] end }
end
# URLごとのロックを管理しているHashを取得し、ブロックの引数に渡して実行する。
# ブロック内では、他のプロセスがそのHashを変更しないようにロックされている。
def mutexes
(@global_mutex ||= Mutex.new).synchronize {
yield(@mutexes ||= TimeLimitedStorage.new(String, Mutex, 60)) } end
# _url_ に対するMutexを作成・ロックして、ブロックを実行する。
# ただし、urlが既にキャッシュにある場合は、Mutexは作成・ロックされず、単にブロックが実行される。
def lock(url, &proc)
mutex = mutexes{ |mutexes|
mutexes[url] ||= Mutex.new if !(expand_by_cache.has_key?(url) || shrink_by_cache.has_key?(url)) }
if mutex
mutex.synchronize(&proc)
else
yield end end
# textからURLを抜き出してすべて短縮したテキストを返す
def shrink_url_all(text)
urls = text.to_enum(:scan, shrinkable_url_regexp).map{ Regexp.last_match.to_s }
return text if(urls.empty?)
table = self.shrink_url(urls)
text.gsub(shrinkable_url_regexp){ |k| table[k] } if table end
# textからURLを抜き出してすべて展開したテキストを返す
def expand_url_all(text)
urls = text.to_enum(:scan, shrinkable_url_regexp).map{ Regexp.last_match.to_s }
return text if(urls.empty?)
table = self.expand_url(urls)
text.gsub(shrinkable_url_regexp){ |k| table[k] } if table end
# URL _url_ を短縮する。urlは配列で渡す。
# { 渡されたURL => 短縮後URL }の配列を返す
def shrink_url(urls)
result = Hash.new
urls.each{ |url|
url.freeze
if shrinked_url?(url)
result[url] = url
else
if(shrink_by_cache[url])
result[url] = shrink_by_cache[url]
else
result[url] = Plugin.filtering(:shrink_url, url).first
cache(result[url], url) if result[url] end end }
result.freeze end
# URL _url_ を展開する。urlは配列で渡す。
# { 渡されたURL => 展開後URL }の配列を返す
def expand_url(urls)
result = Hash.new
urls.each{ |url|
result[url] = expand_url_one(url) }
result.freeze end
# urlを一つだけ受け取り、再帰的に展開する。
# ただし再帰的展開は4段までしか行わず、展開系が渡されたURLと同じになるか
# それ以上展開できなくなれば直ちにそれを返す。
def expand_url_one(url, recur=0)
return expand_by_cache[url] if expand_by_cache[url]
lock(url) do
if recur < 4 and shrinked_url?(url)
expanded = timeout(5, ExpandExpire){ Plugin.filtering(:expand_url, url).first.freeze }
if(expanded == url)
url
else
result = expand_url_one(expanded, recur + 1)
cache(url, result)
result end
else
url end end
rescue ExpandExpire => e
notice "url expand failed: timeout #{url}"
cache(url, url)
url end
def shrinkable_url_regexp
URI.regexp(['http','https']) end
def shrinked_url?(url)
not Plugin.filtering(:is_expanded, url).first
end
end
def shrink_url(url)
nil end
def expand_url(url)
nil end
def shrinked_url?(url)
raise end
def plugin_name
raise end
# no override follow
def shrink_url_ifnecessary(url)
if shrinked_url?(url)
url
else
shrink_url(url)
end
end
end
|