プロジェクト

全般

プロフィール

プラグイン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
修正は以上です.