Plaggerのソースコード読む(4)
今回はPluginのロードについて。
まずload_pluginsメソッド(Plagger.pmです)。
$self->load_plugins(@{ $config->{plugins} || [] });
$config->{plugins}の中身は、(2)の変数をDumpしたのを見たらわかると思う。
一応書くと、
'plugins' => [ { 'config' => { 'feed' => [ { 'url' => 'http://d.hatena.ne.jp/tayaya/rss' } ] }, 'module' => 'Subscription::Config' }, ・ ・ ・ [
こんな感じで、各Pluginの設定が入ってる。
ちなみにGlobalな設定は$self->conf (Plugin側からは$context->conf) でアクセスできます。
load_pluginsの中を見てみると、
my $plugin_path = $self->conf->{plugin_path} || []; $plugin_path = [ $plugin_path ] unless ref $plugin_path;
Globalのplugin_path(外部Pluginのパス)をとってくる。
配列になってるとこからわかるように、plugin_pathは複数設定できるということですね。
次。
for my $path (@$plugin_path) { ・ ・ ・
このforループでは、plugin_pathで設定したディレクトリ内に
PlaggerのPluginファイルがあるかどうかを調べ、
Pluginファイルがあれば$self->plugins_pathに($self->conf->plugin_pathじゃないですよ)
そのパスを設定、ということをやっています。
ここでは
File::SpecやFile::Find::Ruleモジュール、ファイルテスト演算子など、
パスやファイルの扱い方に関して結構勉強になると思うので、
興味のある方はみてみると良いかもしれません。
外部Pluginのパスを設定し終わったら、いよいよconfig.yamlに書いたPluginをロードしていきます。
for my $plugin (@plugins) { $self->load_plugin($plugin) unless $plugin->{disable}; }
@pluginsはload_pluginsを呼ぶときに渡された @{$config->plugins} です。
ではload_pluginの中身。
my $module = delete $config->{module}; $module =~ s/^Plagger::Plugin:://; $module = "Plagger::Plugin::$module";
$moduleに「Plagger::Plugin::*」と、正式名称(?)を代入しています。
$module =~ s/^Plagger::Plugin:://; がなぜ書いてあるかよくわからないのですが、
昔はconfig.yamlには
- module: Plagger::Plugin::Subscription::Config
みたいに書くようにしてたのかな?(古いconfig.yamlと互換性もたせるため?)
とりあえず続き。
if ($module->isa('Plagger::Plugin')) { $self->log(debug => "$module is loaded elsewhere ... maybe .t script?"); } elsif (my $path = $self->plugins_path->{$module}) { eval { require $path } or die $@; } else { $module->require or die $@; }
ここがPluginをロードしてる所です。
まず、
if ($module->isa('Plagger::Plugin')) {
Pluginがすでにロードされているかどうかを調べ、
ロードされていなければ
} elsif (my $path = $self->plugins_path->{$module}) { eval { require $path } or die $@;
外部Pluginのpathをrequire。
どちらでもなければ
$module->require or die $@;
普通にrequireします。
とまあさらっと書いたんですが、
2番目のrequire $pathの部分。
$pathには 'C:\plugin\Sample.pm' みたいにパスが入ってます。
ということは下のようになるわけで、
require 'C:\plugin\Sample.pm';
こんな風にパスでもrequireできるんですね。
初めて知りました。へえー。
最後です。
my $plugin = $module->new($config); $plugin->cache( Plagger::CacheProxy->new($plugin, $self->cache) ); $plugin->register($self); push @{$self->{plugins}}, $plugin;
Pluginをnewし、
cache、registerを呼び出します。
そして
@{$self->{plugins}}にpushして終了です。
registerは重要なので、次回にでも書いていきたいと思います。
ハッカーと画家
![ハッカーと画家 コンピュータ時代の創造者たち ハッカーと画家 コンピュータ時代の創造者たち](https://images-fe.ssl-images-amazon.com/images/I/511SV9NXW2L._SL160_.jpg)
- 作者: ポールグレアム,Paul Graham,川合史朗
- 出版社/メーカー: オーム社
- 発売日: 2005/01/01
- メディア: 単行本
- 購入: 109人 クリック: 4,884回
- この商品を含むブログ (582件) を見る
Plaggerのソースコード読む(3)
眠いので走り書き。
rule: expressionの仕組み。
下記yamlの場合。
(タイトルが qw/perる 日誌/ なfeedだけBreakEntriesToFeedsするyaml)
- module: Filter::BreakEntriesToFeeds rule: expression: $args->{feed}->title eq 'qw/perる 日誌/'
まず、ruleが適用される場所は、Plagger.pmのrun_hookの中のここ。
if ( $plugin->rule->dispatch($plugin, $hook, $args) ) {
$pluginにはPlagger::Plugin::BreakEntriesToFeedsオブジェクトが入ってて、ruleはアクセサ。
$plugin->ruleでPlagger::Rulesオブジェクトが返ってくる。
Rulesオブジェクト作ってるのはPlagger::Pluginのinitメソッド。
sub init { my $self = shift; if (my $rule = $self->{rule}) { $rule = [ $rule ] if ref($rule) eq 'HASH'; my $op = $self->{rule_op}; $self->{rule} = Plagger::Rules->new($op, @$rule);
$self->{rule}には expression => $args->{feed}->title eq 'qw/perる 日誌/' が入ってる。
Plagger::Rules->new。
中でPlagger::Rule->newしてる。
bless { op => uc($op), rules => [ map Plagger::Rule->new($_), @rules ], }, $class;
Plagger::Rule->new。
sub new { my($class, $config) = @_; if (my $exp = $config->{expression}) { $config->{module} = 'Expression'; } my $module = delete $config->{module}; $module = "Plagger::Rule::$module"; $module->require or die $@; my $self = bless {%$config}, $module;
ここでexpression登場。
Plagger::Rule::Expressionをrequireして、
{%$config}とPlagger::Rule::Expressionをblessして返す。
%$configは expression => $args->{feed}->title eq 'qw/perる 日誌/' ですよ。
ここで最初に戻ってみる。
if ( $plugin->rule->dispatch($plugin, $hook, $args) ) {
$plugin->rule->dispatchでPlagger::Rules->dispatchが呼び出される。
その中身。
for my $rule (@{ $self->{rules} }) { push @bool, ($rule->dispatch($args) ? 1 : 0); }
$self->{rules}の中身は[Plagger::Rule::Expressionオブジェクト]。
$argsはPlagger::Feedオブジェクト。
$rule->dispatchでPlagger::Rule::Expression->dispatchを呼び出す。
やっとたどりついた。
Plagger::Rule::Expression->dispatch。
sub dispatch { my($self, $args) = @_; my $status = eval $self->{expression}; if ($@) { Plagger->context->log(error => "Expression error: $@ with '$self->{expression}'"); } $status; }
eval $self->{expression}; で、
$args->{feed}->title eq 'qw/perる 日誌/'
が実行されます。
ここでfeedのタイトルと qw/perる 日誌/ が同じなら真が返る。
まあそのあとごにょごにょしたり(ANDとかの処理)して
真が返ったときのみBreakEntriesToFeedsが適用される、といった感じです。
※ちょいと気になったので注意書き。
rule: expressionでは正規表現とか条件文だけじゃなく、好きなコード書けます。
例えば、
- module: Filter::BreakEntriesToFeeds rule: expression: system 'shutdown -s'
みたいなyamlだとPC終了させられる(shutdown -sはWindowsのshutdownコマンド)。
人のconfig.yamlコピペして使うときは注意したほうが良いかも。
rule: expression
昨日のエントリですが、ブクマでツッコミいただきました。
Rule: expression: $args->{feed}->title で、やれそうな気も
やってみました。
plugins: - module: Subscription::Config config: feed: - url: http://d.hatena.ne.jp/tayaya/rss - module: Filter::BreakEntriesToFeeds rule: expression: $args->{feed}->title ne 'qw/Perる 日誌/' - module: Publish::Gmail
あっさり成功。
どうやらrule: expressionというのは
条件にマッチしたものだけをFilterするようにしてくれるみたい。
上の例だと、feedのタイトルが qw/Perる 日誌/ に一致しないものを
BreakEntriesToFeedsする、ということ。
とても便利。
昨日やってたことは無駄だったんだなあ。
あー恥ずかし。
でもひとつ勉強になりました。
ソース読んでどんな仕組みか眺めてみることにします。
id:otsuneさん、どうもありがとうございました。
Filter::BreakEntriesToFeeds
で特定のfeedだけスキップするようにしてみた。
※追記
rule: expressionで簡単にできます。
このエントリは読む価値なし。
config.yaml
- module: Filter::BreakEntriesToFeeds config: skip: - url: http://d.hatena.ne.jp/tayaya/
Filter::BreakEntriesToFeedsソース
sub break { my($self, $context, $args) = @_; if ( my $skip_items = $self->conf->{skip} ) { $skip_items = [ $skip_items ] unless ref $skip_items; my $url = $args->{feed}->link; if ( grep { $_->{url} eq $url } @$skip_items ) { $context->log(debug => "Skip break entries : $url"); return; } } ・ ・ ・
Alpha Geek Trackerとか、
量が多いのは分割しちゃうとGmailで読み難いので。
※失敗メモ
あーBloglines2Gmailで試したらうまくいかんかった・・・
Subscription::Configなら成功したんだけど。
あしたにでも直す。
$args->{feed}->link?
どうやら$args->{feed}->titleで弾いたほうがよさげ。
でけたー
- module: Filter::BreakEntriesToFeeds config: skip: - title: qw/Perる 日誌/
sub break { my($self, $context, $args) = @_; if ( my $skip_items = $self->conf->{skip} ) { $skip_items = [ $skip_items ] unless ref $skip_items; my $title = $args->{feed}->title; if ( grep { $_->{title} eq $title } @$skip_items ) { $context->log(debug => "Skip break entries : $title"); return; } } ・ ・ ・
Subscription::Bloglinesの既読処理
今日もいつものようにBloglines2Gmail♪
・・・
Plaggerが落ちましたorz
んで、Bloglinesにログインしてみると予想通り全部既読になってました。
Bloglinesにはmark_readというオプションがあるんですが、
これを設定してると(デフォルトではオンになってます)、
更新feedを取得するとその分を既読にしてくれます。
その既読にする処理はsubscription.loadフェーズで行われます。
なので、subscription.loadフェーズの後に
Plaggerが落ちたり他のPluginでdieしたりすると、
Gmailに転送できてないけどBloglines側は全部既読になってしまうわけですね。
しばし考えて、
既読にする処理をplugin.finalizeフェーズに持っていけばいいんじゃね?と思ったのでやってみました。
以下Subscription::Bloglinesのソース。
・register_hookにplugin.finalizeを追加。
$context->register_hook( $self, 'subscription.load' => \¬ifier, 'plugin.finalize' => \&mark_read,
・既読にするよ処理を追加。
sub mark_read { my ($self, $context) =@_; my $mark_read = $self->conf->{mark_read}; $mark_read = 1 unless defined $mark_read; return unless $mark_read; my $count = $self->{bloglines}->notify(); $context->log(debug => "mark $count unread items as read"); eval { $self->{bloglines}->getitems(0, $mark_read) }; }
・subscription.loadフェーズの既読処理をコメントアウト。
(147〜151行目付近)
#} elsif ($mark_read) { # # no error found with XML ... call the API again to mark read # eval { # @updates = $self->{bloglines}->getitems(0, $mark_read); # };
こんな感じなんですがちょっと問題もあって、
Subscription::Bloglinesはfeedを取得しに行くときに、
mark_readオフでfeedを取得→エラーがなければmark_readオンでもう一度feedを取得 getitems(0,0) →getitems(0,$mark_read)
という風にやってます。
なので、
subscription.loadフェーズとplugin.finalizeフェーズの間にものすごく時間がかかった場合、
その間にBloglines側で新しいfeedが更新されちゃったりして、
まだ読んでないfeedまで既読になってしまう可能性があるかも??
う〜ん。
あと例外処理どうしよう。