プロジェクト

全般

プロフィール

プラグインDMSFの検索エンジンをHyper Estraierへ替える

DMSFとRedmineの関係

DMSFはRedmineが標準で備える種々の文書管理を一つにまとめて置き換えようとしたものです.ユーザーインターフェイスは優れていますが,残念ながら検索エンジンにXapianを利用しており,そのままでは日本語に対応できません.そこで,Hyper Estraierへ置き換えるわけですが,改造はエンジンのみの置き換えで済むので,DMSFの基本的な構造は変わりません.

その要点は次のとおりです.

注)$REDMINE_ROOTは自動的に設定される環境変数ではありません.ここでは便宜的に使います.

  1. DMSFが管理する文書*1はすべて $REDMINE_ROOT/files/dmsf へ置かれる.
  2. プロセスが定期的に動作して,インデックスファイル $REDMINE_ROOT/files/index_dmsf を作成する*2

【例】"whitemine" というディレクトリにRedmineがインストールされている場合
ファイル保存フォルダ

インデックスデータベース

これは,DMSFの設定画面にも現れています.またこの画面では,Xapianに由来する設定がありますが,それがHyper Estraierでは不要になります.

Xapianの設定

ここでは,オリジナルを(ちょっと手を抜いた)次の方針で改造することにします.

  1. Web画面に "Xapian" 由来の設定などがあれば,それはそのままにし,削除しない.
  2. Web画面に Estraier に対応するものがあれば,それは "Estraier" へと修正する.(現バージョンではない)
  3. エラーメッセージなどに "Xapian" という文字列があれば,それは "Estraier" へと上書きする.
  4. ソースコード中 "Xapian" を呼び出す部分は削除する.(ロジックの変更)
  5. ソースコード中 "Xapian" という変数名がそのまま "Estraier" へと置き換えられる部分は "Estraier" へと修正する.
  6. ソースコード中 "Xapian" というラベル名称はそのまま利用する.
  7. バージョンは,日本語版であることを示すため,1.yy.zz-JP とする.

さて,変更するファイルは次の3つです.

  1. config/locales/ja.yml
  2. init.rb
  3. app/views/settings/_dmsf_settings.erb
  4. app/models/dmsf_file.rb

config/locales/ja.ymlの修正

コード中のラベルも含めて,Xapian から Estraier へと修正しています.

@@ -156,7 +156,7 @@
   :error_file_storage_directory_does_not_exist: "ファイル保存フォルダが存在せず作ることもできません" 
   :error_file_can_not_be_created: "ファイルを保存フォルダに作ることができません" 
   :error_wrong_zip_encoding: "Zip エンコーディングが正しくありません" 
-  :warning_xapian_not_available: "Xapian が利用できる状態になっていません" 
+  :warning_estraier_not_available: "Hyper Estraier が利用できる状態になっていません" 
   :menu_dmsf: "DMSF" 
   :label_physical_file_delete: "物理ファイルの削除" 
   :user_is_not_project_member: "あなたはプロジェクトのメンバーではありません" 

init.rbの修正

先の方針に基づいて,バージョン番号をユニークにします.

@@ -29,7 +29,7 @@
  name "DMSF" 
  author "Vít Jonáš" 
  description "Document Management System Features" 
-  version "1.2.1" 
+  version "1.2.1-JP" 
  url "http://code.google.com/p/redmine-dmsf/" 
  author_url "mailto:vit.jonas@gmail.com" 

app/views/settings/_dmsf_settings.erb

ロードする(require する)ライブラリを xapian から estraier に変更します.加えて,先の ya.yml に対応する警告のラベルと一致させます.
抽出方針(Stem)に相当する部分も,変数名を xapian から estraier へと修正していますが,Xapian は機能しませんので,これはおまけと考えて下さい.

@@ -75,22 +75,22 @@

 <hr />
 <% begin
-      require 'xapian'
-      xapian_disabled = false
+      require 'estraier'
+      estraier_disabled = false
    rescue LoadError    %>
-    <p class="warning"><%= l(:warning_xapian_not_available) %></p> 
-<%     xapian_disabled = true
+    <p class="warning"><%= l(:warning_estraier_not_available) %></p> 
+<%     estraier_disabled = true
    end %>

 <p>
   <%=content_tag(:label, l(:label_index_database) + ":") %>
-  <%=text_field_tag 'settings[dmsf_index_database]', @settings['dmsf_index_database'], :disabled => xapian_disabled, :size=>50 %><br/>
+  <%=text_field_tag 'settings[dmsf_index_database]', @settings['dmsf_index_database'], :disabled => estraier_disabled, :size=>50 %><br/>
   (<%=l(:label_default)%>: <%="#{RAILS_ROOT}/files/dmsf_index"%>)
 </p>

 <p>
   <%=content_tag(:label, l(:label_stemming_language) + ":") %>
-  <%=text_field_tag 'settings[dmsf_stemming_lang]', @settings['dmsf_stemming_lang'], :disabled => xapian_disabled %><br/>
+  <%=text_field_tag 'settings[dmsf_stemming_lang]', @settings['dmsf_stemming_lang'], :disabled => estraier_disabled %><br/>
   (<%=l(:label_default)%>: english )<br/>
   <br/>
   <%=l(:note_possible_values)%>: danish dutch english finnish french german german2 hungarian italian kraaij_pohlmann lovins norwegian porter portuguese romanian russian spanish swedish turkish (<%=l(:note_pass_none_to_disable_stemming)%>)
@@ -98,9 +98,9 @@

 <p>
   <%=content_tag(:label, l(:label_stem_strategy) + ":")%>
-  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_NONE', @settings['dmsf_stemming_strategy'] == 'STEM_NONE', :disabled => xapian_disabled, :checked=>true  %> <%=l(:option_stem_none)%><br>
-  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_SOME', @settings['dmsf_stemming_strategy'] == 'STEM_SOME', :disabled => xapian_disabled  %> <%=l(:option_stem_some)%><br>
-  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_ALL', @settings['dmsf_stemming_strategy'] == 'STEM_ALL', :disabled => xapian_disabled  %> <%=l(:option_stem_all)%><br>
+  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_NONE', @settings['dmsf_stemming_strategy'] == 'STEM_NONE', :disabled => estraier_disabled, :checked=>true  %> <%=l(:option_stem_none)%><br>
+  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_SOME', @settings['dmsf_stemming_strategy'] == 'STEM_SOME', :disabled => estraier_disabled  %> <%=l(:option_stem_some)%><br>
+  <%=radio_button_tag 'settings[dmsf_stemming_strategy]', 'STEM_ALL', @settings['dmsf_stemming_strategy'] == 'STEM_ALL', :disabled => estraier_disabled  %> <%=l(:option_stem_all)%><br>
   <br/>
   <%=l(:label_stemming_description)%>:
   <br>

app/models/dmsf_file.rb

検索機能本体の修正をします.主な修正の要点は次のとおりです.

XapianではなくてEstraier を呼び出します.呼び出し時のエラー処理はXapianと同様です.
エラーや警告のメッセージも, Estraier のそれへと修正します.
インデックスデータベースへのアクセスメソッドは,Xapianとは大幅に異なりますので,ざっくりと削除し,上書きします.

まず,コードの冒頭には,ライブラリの存在の有無によるエラー処理があります.

@@ -17,11 +17,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

 begin
-  require 'xapian'
-  $xapian_bindings_available = true
+  require 'estraier'
+  $estraier_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." 
-  $xapian_bindings_available = false
+  Rails.logger.info "REDMAIN_ESTRAIER ERROR: No Ruby bindings for Estraier installed !!. PLEASE install Hyper Estraier search engine interface for Ruby." 
+  $estraier_bindings_available = false
 end

次に,インデックスデータベースへのアクセスについて,エラー処理があります.

@@ -260,46 +260,37 @@
       end
     end

-    if !options[:titles_only] && $xapian_bindings_available
+    if !options[:titles_only] && $estraier_bindings_available
       database = nil
       begin
-        database = Xapian::Database.new(Setting.plugin_redmine_dmsf["dmsf_index_database"].strip)
+        database = Estraier::Database::new
+        database.open(Setting.plugin_redmine_dmsf["dmsf_index_database"].strip, Estraier::Database::DBREADER) 
       rescue
-        Rails.logger.warn "REDMAIN_XAPIAN ERROR: Xapian database is not properly set or initiated or is corrupted." 
+        Rails.logger.warn "REDMAIN_ESTRAIER ERROR: Estraier database is not properly set or initiated or is corrupted." 
       end

database.nil 以下は大幅に構造が異なりますので,ざっくりと入れ替えましょう.

       unless database.nil?
-        enquire = Xapian::Enquire.new(database)
+        # create a search condition object
+        cond = Estraier::Condition::new

-        queryString = tokens.join(' ')
-        qp = Xapian::QueryParser.new()
-        stemmer = Xapian::Stem.new(Setting.plugin_redmine_dmsf['dmsf_stemming_lang'].strip)
-        qp.stemmer = stemmer
-        qp.database = database
-        
-        case Setting.plugin_redmine_dmsf['dmsf_stemming_strategy'].strip
-          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 options[:all_words]
-          qp.default_op = Xapian::Query::OP_AND
-        else  
-          qp.default_op = Xapian::Query::OP_OR
-        end
-        
-        query = qp.parse_query(queryString)
-  
-        enquire.query = query
-        matchset = enquire.mset(0, 1000)
-    
-        unless matchset.nil?
-          matchset.matches.each {|m|
-            docdata = m.document.data{url}
-            dochash = Hash[*docdata.scan(/(url|sample|modtime|type|size)=\/?([^\n\]]+)/).flatten]
-            filename = dochash["url"]
-            if !filename.nil?
+        # set the search phrase to the search condition object
+        queryString = tokens.join(options[:all_words] ? ' AND ': ' OR ')
+        cond.set_phrase(queryString )
+
+        # get the result of search
+        result = database.search(cond)
+
+        if result
+          # for each document in the result
+          dnum = result.doc_num
+          for i in 0...dnum
+            # retrieve the document object
+            doc = database.get_doc(result.get_doc_id(i), 0)
+            next unless doc
+            # display attributes
+            uri = doc.attr("@uri")
+            if uri
+              filename = uri.sub(/.*\//, '')
               dmsf_attrs = filename.split("_")
               next if dmsf_attrs[1].blank?
               next unless results.select{|f| f.id.to_s == dmsf_attrs[1]}.empty?

最後にインデックスデータベースを閉じて終了です.


@@ -331,9 +322,14 @@
                 end
               end
             end
-          }
         end
       end    
+
+        # close the database
+        unless database.close
+          Rails.logger.warn(database.err_msg(database.error))
+        end
+      end # unless database.nil?
     end

     [results, results_count]

修正は以上です.