プラグインXapian search pluginの検索エンジンをHyper Estraierへ替える » 履歴 » バージョン 5
Masanori Machii, 2011/10/27 18:00
1 | 1 | Masanori Machii | h1. プラグインXapian search pluginの検索エンジンをHyper Estraierへ替える |
---|---|---|---|
2 | |||
3 | Xapian search pluginは,チケットの添付ファイル内のテキストを検索するプラグインです.これもまたその名が示すように,Xapianを使っており,そのままでは日本語に対応できません. |
||
4 | |||
5 | 3 | Masanori Machii | 加えて,改造はDMSFよりも厄介です.プラグインの名称に,大胆にも Xapian という文字列を使っていることから想像されるように,モジュール名,変数名,メソッド名に現れる xapian を |
6 | すべて estraier に書き換えると,動きません!それを一部ずつ修正しても,かえってバグを混入させてしまいます. |
||
7 | 1 | Masanori Machii | |
8 | 3 | Masanori Machii | なので,修正のポリシーは“[[プラグインDMSFの検索エンジンをHyper Estraierへ替える]]”とは同様にならず,少し複雑です. |
9 | 1 | Masanori Machii | |
10 | 3 | Masanori Machii | まず,同様な方から示すと, |
11 | |||
12 | # Web画面に "Xapian" 由来の設定などがあれば,それはそのままにし,削除しない.(stem,languageらは機能しないまま残る) |
||
13 | 1 | Masanori Machii | # Web画面に Estraier に対応するものがあれば,それは "Estraier" へと修正する.(現バージョンではない) |
14 | # エラーメッセージなどに "Xapian" という文字列があれば,それは "Estraier" へと上書きする. |
||
15 | # ソースコード中 "Xapian" を呼び出す部分は削除する.(ロジックの変更) |
||
16 | 3 | Masanori Machii | # ソースコード中 "Xapian" という変数名を "Estraier" へと置き換える部分を極力少なくする. |
17 | # ソースコード中 "xapian" というラベル名称や,"redmine_xapian" のようにプラグインの由来の変数名があれば,そのまま利用する. |
||
18 | 1 | Masanori Machii | |
19 | |||
20 | 3 | Masanori Machii | 次に複雑な方を示します. |
21 | # 何らの理由により "Estraier" 検索環境が整備できない場合でも,添付ファイルの「タイトルのみ」検索はできるようにする. |
||
22 | # "Xapian search plugin" に,その仕掛けから生じたと思われる,添付ファイル検索の権限に関する外部仕様がある.「文書」と関連するこれを独立させた. |
||
23 | プラグインDMSFの場合は,アップロードしたファイルは,$REDMINE/files よりも一つ下へ下げたディレクトリ dmsf へファイルを置きましたが,添付ファイルはこの files 下へ置かれます.そのインデックスファイルはどこに置かれるべきか?と考えた場合,もちろん files 下へは置けません.なので,一つ上のディレクトリ $REDMINE へ置くことになります.そういうわけで,インデックス・ファイルの名称は files_index になります. |
||
24 | # 検索用インデックスのディレクトリは言語毎に分けない. |
||
25 | # バージョンは,日本語版であることを示すため,1.yy.zz-JP とする. |
||
26 | 1 | Masanori Machii | |
27 | 3 | Masanori Machii | |
28 | さて,変更するファイルは次の6つです. |
||
29 | |||
30 | # config/locales/ja.yml |
||
31 | # init.rb |
||
32 | # app/controllers/search_controller.rb |
||
33 | # app/views/search/index.rhtml |
||
34 | # lib/acts_as_searchable.rb |
||
35 | # lib/xapian_search.rb |
||
36 | |||
37 | |||
38 | h2. config/locales/ja.yml |
||
39 | |||
40 | 奇妙ですが,DMSFの方は最初から ja.yml がありました.ですが,さすがに当該プラグインにはそれはありません.なので,ja.yml は新たに(en.ymlを基にして)作ります. |
||
41 | |||
42 | <pre> |
||
43 | @@ -0,0 +1,29 @@ |
||
44 | +ja: |
||
45 | + |
||
46 | + label_enable_redmine_xapian: "hyper estraier による添付ファイル検索を可能にする" |
||
47 | + label_index_database: "hyper estraier 検索インデックスディレクトリ" |
||
48 | + label_stemming_text: "set the stemming language, the default is 'english'. |
||
49 | + Possible values: danish dutch english finnish french |
||
50 | + german german2 hungarian italian kraaij_pohlmann |
||
51 | + lovins norwegian porter portuguese romanian russian |
||
52 | + spanish swedish turkish (pass 'none' to disable |
||
53 | + stemming)" |
||
54 | + label_search2: "Extended Search" |
||
55 | + 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" |
||
56 | + label_document: "ドキュメント" |
||
57 | + label_issue: "チケット" |
||
58 | + label_wiki: "Wiki" |
||
59 | + label_message: "メッセージ" |
||
60 | + label_article: "Article" |
||
61 | + label_article_plural: "Articles" |
||
62 | + label_stemming_lang: "Stemming Language(注:estraier版では未使用)" |
||
63 | + label_enable_xapian_on_search: "検索画面でhyper estraier使用可" |
||
64 | + label_search_languages: "Stemming languages on search screen(注:estraier版では未使用)" |
||
65 | + label_stemming_strategy: "Stemming strategy(注:estraier版では未使用)" |
||
66 | + label_stem_none: "Stem none" |
||
67 | + label_stem_some: "Stem some" |
||
68 | + label_stem_all: "Stem all" |
||
69 | + label_default_stemming_lang: "Set default stemming lang(注:estraier版では未使用)" |
||
70 | + label_default_stemming_strategy: "Set default stemming strategy(注:estraier版では未使用)" |
||
71 | + label_database_error: "検索インデックスエラー。Hyper Estraier利用環境を構築してください。" |
||
72 | + |
||
73 | </pre> |
||
74 | |||
75 | |||
76 | h2. init.rb |
||
77 | |||
78 | ライブラリ名称を xapian から estraier に変更します.加えて,先の ya.yml に対応する警告のラベルと一致させます. |
||
79 | |||
80 | <pre> |
||
81 | @ -7,10 +7,10 @@ |
||
82 | |||
83 | |||
84 | begin |
||
85 | - require 'xapian' |
||
86 | + require 'estraier' |
||
87 | $xapian_bindings_available = true |
||
88 | rescue LoadError |
||
89 | - Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Xapian installed !!. PLEASE install Xapian search engine interface for Ruby." |
||
90 | + Rails.logger.info "REDMAIN_XAPIAN ERROR: No Ruby bindings for Hyper Estraier installed !!. PLEASE install Hyper Estraier search engine interface for Ruby." |
||
91 | $xapian_bindings_available = false |
||
92 | else |
||
93 | require 'redmine' |
||
94 | @@ -28,7 +28,7 @@ |
||
95 | author_url 'http://undefinederror.org' |
||
96 | |||
97 | description 'With this plugin you will be able to do searches by file name and by strings inside your documents' |
||
98 | - version '1.2.1' |
||
99 | + version '1.2.1-JP' |
||
100 | requires_redmine :version_or_higher => '1.0.0' |
||
101 | |||
102 | settings :partial => 'settings/redmine_xapian_settings', |
||
103 | </pre> |
||
104 | |||
105 | h2. app/controllers/search_controller.rb |
||
106 | |||
107 | 検索エンジンが利用できない場合は,警告メッセージを出力する.なお,「タイトルのみ」検索の場合はこの限りではない. |
||
108 | |||
109 | <pre> |
||
110 | @@ -96,6 +96,7 @@ |
||
111 | end |
||
112 | |||
113 | end |
||
114 | + flash[:warning] = "warning: #{l(:label_database_error)}" unless @titles_only || $xapian_bindings_available |
||
115 | @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} |
||
116 | if params[:previous].nil? |
||
117 | @pagination_previous_date = @results[0].event_datetime if offset && @results[0] |
||
118 | </pre> |
||
119 | |||
120 | |||
121 | h2. app/views/search/index.rhtml |
||
122 | |||
123 | 4 | Masanori Machii | オリジナルでは,検索結果画面に表示される Stemらの設定部分画面を表示されないようにする. |
124 | |||
125 | <pre> |
||
126 | @@ -16,6 +16,7 @@ |
||
127 | <% end %> |
||
128 | </p> |
||
129 | <% logger.debug "DEBUG: object_types from search: " + Redmine::Search.available_search_types.inspect %> |
||
130 | +<% if false then %> |
||
131 | <% 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']) %> |
||
132 | |||
133 | <p> |
||
134 | @@ -31,6 +32,7 @@ |
||
135 | |||
136 | |||
137 | <%end%> |
||
138 | +<%end%> |
||
139 | <p><%= submit_tag l(:button_submit), :name => 'submit' %></p> |
||
140 | <% end %> |
||
141 | </div> |
||
142 | </pre> |
||
143 | |||
144 | |||
145 | 3 | Masanori Machii | h2. lib/acts_as_searchable.rb |
146 | |||
147 | 4 | Masanori Machii | |
148 | サーバー側に検索環境が整備されていない場合でも,少なくともタイトルの検索を可能にする修正. |
||
149 | また,すべての添付ファイルの検索の権限が「文書の閲覧」で集約されていたものを分割する.すなわち,ファイルが添付されているチケットやwiki,それぞれの閲覧権限に従うようにする. |
||
150 | |||
151 | <pre> |
||
152 | @@ -15,7 +15,7 @@ |
||
153 | # along with this program; if not, write to the Free Software |
||
154 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
155 | |||
156 | -require 'xapian_search' |
||
157 | +require 'xapian_search' if $xapian_bindings_available |
||
158 | |||
159 | module Redmine |
||
160 | module Acts |
||
161 | @@ -116,6 +116,10 @@ |
||
162 | #Attahcment on documents |
||
163 | results_doc= [] |
||
164 | results_count_doc=0 |
||
165 | + project_conditions = [] |
||
166 | + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : |
||
167 | + Project.allowed_to_condition(User.current, :view_documents)) |
||
168 | + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? |
||
169 | find_options_tmp=Hash.new |
||
170 | find_options_tmp=find_options_tmp.merge(find_options) |
||
171 | find_options_tmp[:conditions] = merge_conditions (find_options_tmp[:conditions], :container_type=>"Document" ) |
||
172 | @@ -129,6 +133,10 @@ |
||
173 | results +=results_doc |
||
174 | results_count += results_count_doc |
||
175 | #Attachemnts on WikiPage |
||
176 | + project_conditions = [] |
||
177 | + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : |
||
178 | + Project.allowed_to_condition(User.current, :view_wiki_pages)) |
||
179 | + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? |
||
180 | find_options_tmp=Hash.new |
||
181 | find_options_tmp=find_options_tmp.merge(find_options) |
||
182 | results_wiki= [] |
||
183 | @@ -146,6 +154,10 @@ |
||
184 | results+=results_wiki |
||
185 | results_count+= results_count_wiki |
||
186 | #Attachemnts on Message |
||
187 | + project_conditions = [] |
||
188 | + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : |
||
189 | + Project.allowed_to_condition(User.current, :view_messages)) |
||
190 | + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? |
||
191 | find_options_tmp =Hash.new |
||
192 | find_options_tmp=find_options_tmp.merge(find_options) |
||
193 | results_message= [] |
||
194 | @@ -163,6 +175,10 @@ |
||
195 | results+=results_message |
||
196 | results_count+= results_count_message |
||
197 | #Attachemnts on Issues |
||
198 | + project_conditions = [] |
||
199 | + project_conditions << (searchable_options[:permission].nil? ? Project.visible_by(User.current) : |
||
200 | + Project.allowed_to_condition(User.current, :view_issues)) |
||
201 | + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? |
||
202 | find_options_tmp =Hash.new |
||
203 | find_options_tmp=find_options_tmp.merge(find_options) |
||
204 | results_issue= [] |
||
205 | @@ -180,6 +196,9 @@ |
||
206 | #Attachments on Articles |
||
207 | if Redmine::Search.available_search_types.include?("articles") |
||
208 | logger.debug "DEBUG: knowledgebase plugin installed" |
||
209 | + project_conditions = [] |
||
210 | + project_conditions << Project.visible_by(User.current) |
||
211 | + project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil? |
||
212 | find_options_tmp=Hash.new |
||
213 | find_options_tmp=find_options_tmp.merge(find_options) |
||
214 | results_article = [] |
||
215 | @@ -194,7 +213,7 @@ |
||
216 | results_count += results_count_article |
||
217 | end |
||
218 | # Search attachments over xapian |
||
219 | - if !options[:titles_only] |
||
220 | + if !options[:titles_only] && $xapian_bindings_available |
||
221 | begin |
||
222 | xapianresults, xapianresults_count = XapianSearch.search_attachments( tokens, limit_options, |
||
223 | options[:offset], projects, options[:all_words], options[:user_stem_lang], |
||
224 | </pre> |
||
225 | |||
226 | 3 | Masanori Machii | h2. lib/xapian_search.rb |
227 | 4 | Masanori Machii | |
228 | 【注意】このファイルについては,後のコードレビューにてさらに変更する可能性があります.特に権限の部分がそうです. |
||
229 | |||
230 | 検索インデックスは言語毎に分けてはいない. |
||
231 | |||
232 | <pre> |
||
233 | @@ -11,69 +11,56 @@ |
||
234 | Rails.logger.debug "DEBUG: user_stem_lang: " + user_stem_lang.inspect |
||
235 | Rails.logger.debug "DEBUG: user_stem_strategy: " + user_stem_strategy.inspect |
||
236 | Rails.logger.debug "DEBUG: databasepath: " + getDatabasePath(user_stem_lang) |
||
237 | - databasepath = getDatabasePath(user_stem_lang) |
||
238 | + databasepath = getDatabasePath('') |
||
239 | |||
240 | begin |
||
241 | - database = Xapian::Database.new(databasepath) |
||
242 | + database = Estraier::Database::new |
||
243 | + unless database.open(databasepath, Estraier::Database::DBREADER) |
||
244 | + return [xpattachments,0] |
||
245 | + end |
||
246 | rescue => error |
||
247 | raise databasepath |
||
248 | - return [xpattachments,0] |
||
249 | end |
||
250 | |||
251 | # Start an enquire session. |
||
252 | |||
253 | - enquire = Xapian::Enquire.new(database) |
||
254 | + enquire = Estraier::Condition::new |
||
255 | + |
||
256 | + queryString = tokens.join(all_words ? ' AND ': ' OR ') |
||
257 | + enquire.set_phrase(queryString) |
||
258 | + enquire.set_max(100) |
||
259 | |||
260 | - # Combine the rest of the command line arguments with spaces between |
||
261 | - # them, so that simple queries don't have to be quoted at the shell |
||
262 | - # level. |
||
263 | - #queryString = ARGV[1..-1].join(' ') |
||
264 | - queryString = tokens.join(' ') |
||
265 | - # Parse the query string to produce a Xapian::Query object. |
||
266 | - qp = Xapian::QueryParser.new() |
||
267 | - stemmer = Xapian::Stem.new($user_stem_lang) |
||
268 | - qp.stemmer = stemmer |
||
269 | - qp.database = database |
||
270 | - case @user_stem_strategy |
||
271 | - when "STEM_NONE" then qp.stemming_strategy = Xapian::QueryParser::STEM_NONE |
||
272 | - when "STEM_SOME" then qp.stemming_strategy = Xapian::QueryParser::STEM_SOME |
||
273 | - when "STEM_ALL" then qp.stemming_strategy = Xapian::QueryParser::STEM_ALL |
||
274 | - end |
||
275 | - if all_words |
||
276 | - qp.default_op = Xapian::Query::OP_AND |
||
277 | - else |
||
278 | - qp.default_op = Xapian::Query::OP_OR |
||
279 | - end |
||
280 | - query = qp.parse_query(queryString) |
||
281 | Rails.logger.debug "DEBUG queryString is: #{queryString}" |
||
282 | - Rails.logger.debug "DEBUG: Parsed query is: #{query.description()} " |
||
283 | |||
284 | - # Find the top 100 results for the query. |
||
285 | - enquire.query = query |
||
286 | - matchset = enquire.mset(0, 1000) |
||
287 | + matchset = database.search(enquire) |
||
288 | |||
289 | return [xpattachments,0] if matchset.nil? |
||
290 | |||
291 | # Display the results. |
||
292 | - #logger.debug "#{@matchset.matches_estimated()} results found." |
||
293 | - Rails.logger.debug "DEBUG: Matches 1-#{matchset.size}:¥n" |
||
294 | + Rails.logger.debug "DEBUG: Matches 1-#{matchset.doc_num}:¥n" |
||
295 | |||
296 | - matchset.matches.each {|m| |
||
297 | + dnum = matchset.doc_num |
||
298 | + for i in 0...dnum |
||
299 | + doc = database.get_doc(matchset.get_doc_id(i), 0) |
||
300 | + next unless doc |
||
301 | #Rails.logger.debug "#{m.rank + 1}: #{m.percent}% docid=#{m.docid} [#{m.document.data}]¥n" |
||
302 | #logger.debug "DEBUG: m: " + m.document.data.inspect |
||
303 | - docdata=m.document.data{url} |
||
304 | - dochash=Hash[*docdata.scan(/(url|sample|modtime|type|size)=¥/?([^¥n¥]]+)/).flatten] |
||
305 | - if not dochash.nil? then |
||
306 | - find_conditions = Attachment.merge_conditions (limit_options[:conditions], :disk_filename => dochash.fetch('url') ) |
||
307 | + |
||
308 | + uri = doc.attr("@uri") |
||
309 | + if uri then |
||
310 | + filename = uri.sub(/.*¥//, '') |
||
311 | + |
||
312 | + find_conditions = Attachment.merge_conditions (limit_options[:conditions], :disk_filename => filename) |
||
313 | docattach=Attachment.find (:first, :conditions => find_conditions ) |
||
314 | if not docattach.nil? then |
||
315 | if docattach["container_type"] == "Article" and not Redmine::Search.available_search_types.include?("articles") |
||
316 | Rails.logger.debug "DEBUG: Knowledgebase plugin in not installed.." |
||
317 | elsif not docattach.container.nil? then |
||
318 | Rails.logger.debug "DEBUG: adding attach.. " |
||
319 | - allowed = User.current.allowed_to?("view_documents".to_sym, docattach.container.project) || docattach.container_type == "Article" |
||
320 | + # allowed = User.current.allowed_to?("view_documents".to_sym, docattach.container.project) || docattach.container_type == "Article" |
||
321 | + allowed = true |
||
322 | if ( allowed and project_included(docattach.container.project.id, projects_to_search ) ) |
||
323 | - docattach[:description]=dochash["sample"] |
||
324 | + docattach[:description] = doc.make_snippet(tokens,50,0,10).to_s |
||
325 | xpattachments.push ( docattach ) |
||
326 | else |
||
327 | Rails.logger.debug "DEBUG: user without permissions" |
||
328 | @@ -81,7 +68,10 @@ |
||
329 | end |
||
330 | end |
||
331 | end |
||
332 | - } |
||
333 | + end |
||
334 | + |
||
335 | + database.close |
||
336 | + |
||
337 | @@numattach=xpattachments.size if offset.nil? |
||
338 | xpattachments=xpattachments.sort_by{|x| x[:created_on] } |
||
339 | [xpattachments, @@numattach] |
||
340 | </pre> |
||
341 | 5 | Masanori Machii | |
342 | |||
343 | 修正は以上です. |