プラグインXapian search pluginの検索エンジンをHyper Estraierへ替える¶
Xapian search pluginは,チケットの添付ファイル内のテキストを検索するプラグインです.これもまたその名が示すように,Xapianを使っており,そのままでは日本語に対応できません.
加えて,改造はDMSFよりも厄介です.プラグインの名称に,大胆にも Xapian という文字列を使っていることから想像されるように,モジュール名,変数名,メソッド名に現れる xapian を
すべて estraier に書き換えると,動きません!それを一部ずつ修正しても,かえってバグを混入させてしまいます.
なので,修正のポリシーは“プラグインDMSFの検索エンジンをHyper Estraierへ替える”とは同様にならず,少し複雑です.
まず,同様な方から示すと,
- Web画面に "Xapian" 由来の設定などがあれば,それはそのままにし,削除しない.(stem,languageらは機能しないまま残る)
- Web画面に Estraier に対応するものがあれば,それは "Estraier" へと修正する.(現バージョンではない)
- エラーメッセージなどに "Xapian" という文字列があれば,それは "Estraier" へと上書きする.
- ソースコード中 "Xapian" を呼び出す部分は削除する.(ロジックの変更)
- ソースコード中 "Xapian" という変数名を "Estraier" へと置き換える部分を極力少なくする.
- ソースコード中 "xapian" というラベル名称や,"redmine_xapian" のようにプラグインの由来の変数名があれば,そのまま利用する.
- 何らの理由により "Estraier" 検索環境が整備できない場合でも,添付ファイルの「タイトルのみ」検索はできるようにする.
- "Xapian search plugin" に,その仕掛けから生じたと思われる,添付ファイル検索の権限に関する外部仕様がある.「文書」と関連するこれを独立させた.
プラグインDMSFの場合は,アップロードしたファイルは,$REDMINE/files よりも一つ下へ下げたディレクトリ dmsf へファイルを置きましたが,添付ファイルはこの files 下へ置かれます.そのインデックスファイルはどこに置かれるべきか?と考えた場合,もちろん files 下へは置けません.なので,一つ上のディレクトリ $REDMINE へ置くことになります.そういうわけで,インデックス・ファイルの名称は files_index になります. - 検索用インデックスのディレクトリは言語毎に分けない.
- バージョンは,日本語版であることを示すため,1.yy.zz-JP とする.
さて,変更するファイルは次の6つです.
- config/locales/ja.yml
- init.rb
- app/controllers/search_controller.rb
- app/views/search/index.rhtml
- lib/acts_as_searchable.rb
- lib/xapian_search.rb
ところで,本サイトからコピーする場合は "¥"(バックスラッシュ)に注意して下さい.円記号ではありません.
config/locales/ja.yml¶
奇妙ですが,DMSFの方は最初から ja.yml がありました.ですが,さすがに当該プラグインにはそれはありません.なので,ja.yml は新たに(en.ymlを基にして)作ります.
@@ -0,0 +1,29 @@ +ja: + + label_enable_redmine_xapian: "hyper estraier による添付ファイル検索を可能にする" + label_index_database: "hyper estraier 検索インデックスディレクトリ" + label_stemming_text: "set the stemming language, the default is 'english'. + Possible values: danish dutch english finnish french + german german2 hungarian italian kraaij_pohlmann + lovins norwegian porter portuguese romanian russian + spanish swedish turkish (pass 'none' to disable + stemming)" + label_search2: "Extended Search" + text_search1: "This plugin does same redmine search plus searches into attachment. To enable xapian you have to build database with omindex utility and configure index database on plugin settings page" + label_document: "ドキュメント" + label_issue: "チケット" + label_wiki: "Wiki" + label_message: "メッセージ" + label_article: "Article" + label_article_plural: "Articles" + label_stemming_lang: "Stemming Language(注:estraier版では未使用)" + label_enable_xapian_on_search: "検索画面でhyper estraier使用可" + label_search_languages: "Stemming languages on search screen(注:estraier版では未使用)" + label_stemming_strategy: "Stemming strategy(注:estraier版では未使用)" + label_stem_none: "Stem none" + label_stem_some: "Stem some" + label_stem_all: "Stem all" + label_default_stemming_lang: "Set default stemming lang(注:estraier版では未使用)" + label_default_stemming_strategy: "Set default stemming strategy(注:estraier版では未使用)" + label_database_error: "検索インデックスエラー。Hyper Estraier利用環境を構築してください。" +
init.rb¶
ライブラリ名称を xapian から estraier に変更します.加えて,先の ya.yml に対応する警告のラベルと一致させます.
@ -7,10 +7,10 @@ begin - require 'xapian' + require 'estraier' $xapian_bindings_available = true rescue LoadError - Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Xapian installed !!. PLEASE install Xapian search engine interface for Ruby." + Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Hyper Estraier installed !!. PLEASE install Hyper Estraier search engine interface for Ruby." $xapian_bindings_available = false else require 'redmine' @@ -28,7 +28,7 @@ author_url 'http://undefinederror.org' description 'With this plugin you will be able to do searches by file name and by strings inside your documents' - version '1.2.1' + version '1.2.1-JP' requires_redmine :version_or_higher => '1.0.0' settings :partial => 'settings/redmine_xapian_settings',
app/controllers/search_controller.rb¶
検索エンジンが利用できない場合は,警告メッセージを出力する.なお,「タイトルのみ」検索の場合はこの限りではない.
@@ -96,6 +96,7 @@ end end + flash[:warning] = "warning: #{l(:label_database_error)}" unless @titles_only || $xapian_bindings_available @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} if params[:previous].nil? @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
app/views/search/index.rhtml¶
オリジナルでは,検索結果画面に表示される Stemらの設定部分画面を表示されないようにする.
@@ -16,6 +16,7 @@ <% end %> </p> <% logger.debug "DEBUG: object_types from search: " + Redmine::Search.available_search_types.inspect %> +<% if false then %> <% Setting.plugin_redmine_xapian['stem_langs'].push(Setting.plugin_redmine_xapian['stemming_lang']) unless Setting.plugin_redmine_xapian['stem_langs'].include?(Setting.plugin_redmine_xapian['stemming_lang']) %> <p> @@ -31,6 +32,7 @@ <%end%> +<%end%> <p><%= submit_tag l(:button_submit), :name => 'submit' %></p> <% end %> </div>
lib/acts_as_searchable.rb¶
サーバー側に検索環境が整備されていない場合でも,少なくともタイトルの検索を可能にする修正.
また,すべての添付ファイルの検索の権限が「文書の閲覧」で集約されていたものを分割する.すなわち,ファイルが添付されているチケットやwiki,それぞれの閲覧権限に従うようにする.
@@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'xapian_search' +require 'xapian_search' if $xapian_bindings_available module Redmine module Acts @@ -116,6 +116,10 @@ #Attahcment on documents results_doc= [] results_count_doc=0 + project_conditions = [] + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : + Project.allowed_to_condition(User.current, :view_documents)) + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? find_options_tmp=Hash.new find_options_tmp=find_options_tmp.merge(find_options) find_options_tmp[:conditions] = merge_conditions (find_options_tmp[:conditions], :container_type=>"Document" ) @@ -129,6 +133,10 @@ results +=results_doc results_count += results_count_doc #Attachemnts on WikiPage + project_conditions = [] + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : + Project.allowed_to_condition(User.current, :view_wiki_pages)) + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? find_options_tmp=Hash.new find_options_tmp=find_options_tmp.merge(find_options) results_wiki= [] @@ -146,6 +154,10 @@ results+=results_wiki results_count+= results_count_wiki #Attachemnts on Message + project_conditions = [] + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : + Project.allowed_to_condition(User.current, :view_messages)) + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? find_options_tmp =Hash.new find_options_tmp=find_options_tmp.merge(find_options) results_message= [] @@ -163,6 +175,10 @@ results+=results_message results_count+= results_count_message #Attachemnts on Issues + project_conditions = [] + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : + Project.allowed_to_condition(User.current, :view_issues)) + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? find_options_tmp =Hash.new find_options_tmp=find_options_tmp.merge(find_options) results_issue= [] @@ -180,6 +196,9 @@ #Attachments on Articles if Redmine::Search.available_search_types.include?("articles") logger.debug "DEBUG: knowledgebase plugin installed" + project_conditions = [] + project_conditions << Project.visible_by(User.current) + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? find_options_tmp=Hash.new find_options_tmp=find_options_tmp.merge(find_options) results_article = [] @@ -194,7 +213,7 @@ results_count += results_count_article end # Search attachments over xapian - if !options[:titles_only] + if !options[:titles_only] && $xapian_bindings_available begin xapianresults, xapianresults_count = XapianSearch.search_attachments( tokens, limit_options, options[:offset], projects, options[:all_words], options[:user_stem_lang],
lib/xapian_search.rb¶
【注意】このファイルについては,後のコードレビューにてさらに変更する可能性があります.特に権限の部分がそうです.
検索インデックスは言語毎に分けてはいない.
@@ -11,69 +11,56 @@ Rails.logger.debug "DEBUG: user_stem_lang: " + user_stem_lang.inspect Rails.logger.debug "DEBUG: user_stem_strategy: " + user_stem_strategy.inspect Rails.logger.debug "DEBUG: databasepath: " + getDatabasePath(user_stem_lang) - databasepath = getDatabasePath(user_stem_lang) + databasepath = getDatabasePath('') begin - database = Xapian::Database.new(databasepath) + database = Estraier::Database::new + unless database.open(databasepath, Estraier::Database::DBREADER) + return [xpattachments,0] + end rescue => error raise databasepath - return [xpattachments,0] end # Start an enquire session. - enquire = Xapian::Enquire.new(database) + enquire = Estraier::Condition::new + + queryString = tokens.join(all_words ? ' AND ': ' OR ') + enquire.set_phrase(queryString) + enquire.set_max(100) - # Combine the rest of the command line arguments with spaces between - # them, so that simple queries don't have to be quoted at the shell - # level. - #queryString = ARGV[1..-1].join(' ') - queryString = tokens.join(' ') - # Parse the query string to produce a Xapian::Query object. - qp = Xapian::QueryParser.new() - stemmer = Xapian::Stem.new($user_stem_lang) - qp.stemmer = stemmer - qp.database = database - case @user_stem_strategy - when "STEM_NONE" then qp.stemming_strategy = Xapian::QueryParser::STEM_NONE - when "STEM_SOME" then qp.stemming_strategy = Xapian::QueryParser::STEM_SOME - when "STEM_ALL" then qp.stemming_strategy = Xapian::QueryParser::STEM_ALL - end - if all_words - qp.default_op = Xapian::Query::OP_AND - else - qp.default_op = Xapian::Query::OP_OR - end - query = qp.parse_query(queryString) Rails.logger.debug "DEBUG queryString is: #{queryString}" - Rails.logger.debug "DEBUG: Parsed query is: #{query.description()} " - # Find the top 100 results for the query. - enquire.query = query - matchset = enquire.mset(0, 1000) + matchset = database.search(enquire) return [xpattachments,0] if matchset.nil? # Display the results. - #logger.debug "#{@matchset.matches_estimated()} results found." - Rails.logger.debug "DEBUG: Matches 1-#{matchset.size}:¥n" + Rails.logger.debug "DEBUG: Matches 1-#{matchset.doc_num}:¥n" - matchset.matches.each {|m| + dnum = matchset.doc_num + for i in 0...dnum + doc = database.get_doc(matchset.get_doc_id(i), 0) + next unless doc #Rails.logger.debug "#{m.rank + 1}: #{m.percent}% docid=#{m.docid} [#{m.document.data}]¥n" #logger.debug "DEBUG: m: " + m.document.data.inspect - docdata=m.document.data{url} - dochash=Hash[*docdata.scan(/(url|sample|modtime|type|size)=¥/?([^¥n¥]]+)/).flatten] - if not dochash.nil? then - find_conditions = Attachment.merge_conditions (limit_options[:conditions], :disk_filename => dochash.fetch('url') ) + + uri = doc.attr("@uri") + if uri then + filename = uri.sub(/.*¥//, '') + + find_conditions = Attachment.merge_conditions (limit_options[:conditions], :disk_filename => filename) docattach=Attachment.find (:first, :conditions => find_conditions ) if not docattach.nil? then if docattach["container_type"] == "Article" and not Redmine::Search.available_search_types.include?("articles") Rails.logger.debug "DEBUG: Knowledgebase plugin in not installed.." elsif not docattach.container.nil? then Rails.logger.debug "DEBUG: adding attach.. " - allowed = User.current.allowed_to?("view_documents".to_sym, docattach.container.project) || docattach.container_type == "Article" + # allowed = User.current.allowed_to?("view_documents".to_sym, docattach.container.project) || docattach.container_type == "Article" + allowed = true if ( allowed and project_included(docattach.container.project.id, projects_to_search ) ) - docattach[:description]=dochash["sample"] + docattach[:description] = doc.make_snippet(tokens,50,0,10).to_s xpattachments.push ( docattach ) else Rails.logger.debug "DEBUG: user without permissions" @@ -81,7 +68,10 @@ end end end - } + end + + database.close + @@numattach=xpattachments.size if offset.nil? xpattachments=xpattachments.sort_by{|x| x[:created_on] } [xpattachments, @@numattach]
修正は以上です.
Redmine-1.3へ対応したバージョン 1.2.3 の場合の差分ファイルを添付しておきます.
Updated by Masanori Machii over 12 years ago · 9 revisions