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