aboutsummaryrefslogtreecommitdiffstats
path: root/core/messageconverters.rb
blob: 7ead8521b1eaa67aa4a38326c2aab6213ee9fd30 (plain)
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