diff options
author | Toshiaki Asai <toshi.alternative@gmail.com> | 2016-11-15 16:17:36 +0900 |
---|---|---|
committer | Toshiaki Asai <toshi.alternative@gmail.com> | 2016-11-27 11:43:00 +0900 |
commit | 129323d7be734b9778607063b9a40418f5cd880b (patch) | |
tree | 4b93e808941b3ced696a30bda457d1b1124b9c54 | |
parent | e3f5cabd7fde059713f07af415ec8b423ed577cc (diff) | |
download | mikutter-129323d7be734b9778607063b9a40418f5cd880b.tar.gz |
openimgのPhotoModelのメイン処理を分離 refs #934
openimgに依存しない独自のPhotoModelを定義できるようになった
-rw-r--r-- | core/lib/retriever/mixin/photo_mixin.rb | 148 | ||||
-rw-r--r-- | core/plugin/openimg/model/photo.rb | 144 |
2 files changed, 161 insertions, 131 deletions
diff --git a/core/lib/retriever/mixin/photo_mixin.rb b/core/lib/retriever/mixin/photo_mixin.rb new file mode 100644 index 00000000..3867d726 --- /dev/null +++ b/core/lib/retriever/mixin/photo_mixin.rb @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- + +=begin rdoc +画像リソースを扱うModelのためのmix-in。 +これをincludeすると、画像データを保存するblobフィールドが追加される。 + +このmoduleをincludeしたクラスは、必要に応じて _download_routine_ をオーバライドする +=end +module Retriever::PhotoMixin + def self.included(klass) + klass.field.string :blob + end + + # 画像をダウンロードする。 + # partialを指定すると、ダウンロードの進捗があれば、前回呼び出されたときから + # ダウンロードできた内容を引数に呼び出される。 + # 既にダウンロードが終わっていれば、 _blob_ の戻り値がそのまま渡される。 + # このメソッドは、複数回呼び出されても画像のダウンロードを一度しか行わない。 + # ==== Args + # [&partial_callback] 現在ダウンロードできたデータの一部(String) + # ==== Return + # [Delayer::Deferred::Deferredable] ダウンロードが完了したらselfを引数に呼び出される + def download(&partial_callback) # :yield: part + case @state + when :complete + partial_callback.(blob) if block_given? + Delayer::Deferred.new.next{ self } + when :download + append_download_queue(&partial_callback) + else + download!(&partial_callback) + end + end + + # 画像のダウンロードが終わっていれば真を返す。 + # 真を返す時、 _blob_ には完全な画像の情報が存在している + def completed? + @state == :complete + end + + # 画像をダウロード中なら真 + def downloading? + @state == :download + end + + # ダウンロードが始まっていなければ真 + def ready? + !@state + end + + def inspect + if @state == :complete + "#<#{self.class}: #{uri} (state: #{@state}, #{self.blob.size} bytes cached)>" + else + "#<#{self.class}: #{uri} (state: #{@state})>" + end + end + + private + + def download!(&partial_callback) + atomic do + return download(&partial_callback) unless ready? + promise = initialize_download(&partial_callback) + Thread.new(&method(:download_routine)).next{|success| + if success + finalize_download_as_success + else + Delayer::Deferred.fail false + end + }.trap{|exception| + finalize_download_as_fail(exception) + }.terminate('error') + promise + end + end + + def append_download_queue(&partial_callback) + atomic do + return download(&partial_callback) unless downloading? + register_partial_callback(partial_callback) + register_promise + end + end + + def register_promise + promise = Delayer::Deferred.new(true) + (@promises ||= Set.new) << promise + promise + end + + def register_partial_callback(cb) + @partials ||= Set.new + if cb + @partials << cb + cb.(@buffer) if !@buffer.empty? + end + end + + def download_routine + begin + open(uri.to_s) do |is| + download_mainloop(is) + end + rescue EOFError + true + end + end + + # _input_stream_ から、画像をダウンロードし、 _@buffer_ に格納する。 + # このインスタンスの _download_ メソッドが既に呼ばれていて、ブロックが渡されている場合、 + # そのブロックに一定の間隔でダウンロードしたデータを渡す。 + # PhotoMixinをincludeしたクラスでオーバライドされた _download_routine_ から呼ばれることを想定している。 + # ==== Args + # [input_stream] 画像データがどんどん出てくる IO のインスタンス + def download_mainloop(input_stream) + loop do + Thread.pass + partial = input_stream.readpartial(1024**2).freeze + @buffer << partial + atomic{ @partials.each{|c|c.(partial)} } + end + end + + def initialize_download(&partial_callback) + @state = :download + @buffer = String.new + register_partial_callback(partial_callback) + register_promise + end + + def finalize_download_as_success + atomic do + self.blob = @buffer.freeze + @state = :complete + @promises.each{|p| p.call(self) } + @buffer = @promises = @partials = nil + end + end + + def finalize_download_as_fail(exception) + atomic do + @state = nil + @promises.each{|p| p.fail(exception) } + @buffer = @promises = @partials = nil + end + end +end diff --git a/core/plugin/openimg/model/photo.rb b/core/plugin/openimg/model/photo.rb index b0e61e3c..25d185c9 100644 --- a/core/plugin/openimg/model/photo.rb +++ b/core/plugin/openimg/model/photo.rb @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +miquire :lib, 'retriever/mixin/photo_mixin' module Plugin::Openimg class Photo < Retriever::Model + include Retriever::PhotoMixin register :openimg_photo, name: Plugin[:openimg]._('画像ビューア') field.uri :perma_link - field.string :blob handle ->uri{ uri_str = uri.to_s @@ -15,139 +16,20 @@ module Plugin::Openimg new(perma_link: uri) end - # 画像をダウンロードする。 - # partialを指定すると、ダウンロードの進捗があれば、前回呼び出されたときから - # ダウンロードできた内容を引数に呼び出される。 - # 既にダウンロードが終わっていれば、 _blob_ の戻り値がそのまま渡される。 - # このメソッドは、複数回呼び出されても画像のダウンロードを一度しか行わない。 - # ==== Args - # [&partial_callback] 現在ダウンロードできたデータの一部(String) - # ==== Return - # [Delayer::Deferred::Deferredable] ダウンロードが完了したらselfを引数に呼び出される - def download(&partial_callback) # :yield: part - case @state - when :complete - partial_callback.(blob) if block_given? - Delayer::Deferred.new.next{ self } - when :download - append_download_queue(&partial_callback) - else - download!(&partial_callback) - end - end - - # 画像のダウンロードが終わっていれば真を返す。 - # 真を返す時、 _blob_ には完全な画像の情報が存在している - def completed? - @state == :complete - end - - # 画像をダウロード中なら真 - def downloading? - @state == :download - end - - # ダウンロードが始まっていなければ真 - def ready? - !@state - end - - def inspect - if @state == :complete - "#<#{self.class}: #{perma_link} (state: #{@state}, #{self.blob.size} bytes cached)>" - else - "#<#{self.class}: #{perma_link} (state: #{@state})>" - end - end - private - def download!(&partial_callback) - atomic do - return download(&partial_callback) unless ready? - promise = initialize_download(&partial_callback) - Thread.new(&gen_download_routine).next{|success| - if success - finalize_download_as_success - else - Delayer::Deferred.fail false - end - }.trap{|exception| - finalize_download_as_fail(exception) - }.terminate('error') - promise - end - end - - def append_download_queue(&partial_callback) - atomic do - return download(&partial_callback) unless downloading? - register_partial_callback(partial_callback) - register_promise - end - end - - def register_promise - promise = Delayer::Deferred.new(true) - (@promises ||= Set.new) << promise - promise - end - - def register_partial_callback(cb) - @partials ||= Set.new - if cb - @partials << cb - cb.(@buffer) if !@buffer.empty? - end - end - - def gen_download_routine - -> do - begin - _, raw = Plugin.filtering(:openimg_raw_image_from_display_url, perma_link.to_s, nil) - if raw - download_mainloop(raw) - else - raise "couldn't resolve actual image url of #{perma_link}." - end - rescue EOFError - true - ensure - raw.close rescue nil + def download_routine + begin + _, raw = Plugin.filtering(:openimg_raw_image_from_display_url, perma_link.to_s, nil) + if raw + download_mainloop(raw) + else + raise "couldn't resolve actual image url of #{perma_link}." end - end - end - - def download_mainloop(raw) - loop do - Thread.pass - partial = raw.readpartial(1024**2).freeze - @buffer << partial - atomic{ @partials.each{|c|c.(partial)} } - end - end - - def initialize_download(&partial_callback) - @state = :download - @buffer = String.new - register_partial_callback(partial_callback) - register_promise - end - - def finalize_download_as_success - atomic do - self.blob = @buffer.freeze - @state = :complete - @promises.each{|p| p.call(self) } - @buffer = @promises = @partials = nil - end - end - - def finalize_download_as_fail(exception) - atomic do - @state = nil - @promises.each{|p| p.fail(exception) } - @buffer = @promises = @partials = nil + rescue EOFError + true + ensure + raw.close rescue nil end end end |