Class: ActsAsXapian::WriteableIndex

Inherits:
Index
  • Object
show all
Defined in:
lib/acts_as_xapian/writeable_index.rb

Constant Summary collapse

@@writable_db =
nil
@@writable_suffix =
nil

Class Method Summary collapse

Methods inherited from Index

init, prepare_environment

Class Method Details

.delete_document(*args) ⇒ Object



9
10
11
# File 'lib/acts_as_xapian/writeable_index.rb', line 9

def delete_document(*args)
  @@writable_db.delete_document(*args)
end

.rebuild_index(model_classes, verbose = false) ⇒ Object

You must specify all the models here, this totally rebuilds the Xapian database. You’ll want any readers to reopen the database after this.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/acts_as_xapian/writeable_index.rb', line 98

def rebuild_index(model_classes, verbose = false)
  raise "when rebuilding all, please call as first and only thing done in process / task" unless @@writable_db.nil?

  prepare_environment

  # Delete any existing .new database, and open a new one
  new_path = "#{self.db_path}.new"
  if File.exist?(new_path)
    raise "found existing #{new_path} which is not Xapian flint database, please delete for me" unless File.exist?(File.join(new_path, "iamflint"))
    FileUtils.rm_r(new_path)
  end
  self.writable_init(".new")

  # Index everything

  most_recent_job = ActsAsXapianJob.find(:first, :order => 'id DESC')
  batch_size = 1000
  model_classes.each do |model_class|
    all_ids = model_class.find(:all, :select => model_class.primary_key, :order => model_class.primary_key).map {|i| i.id }
    all_ids.each_slice(batch_size) do |ids|
      puts "ActsAsXapian::WriteableIndex: New batch. Including ids #{ids.first} to #{ids.last}" if verbose
      models = model_class.find(:all, :conditions => {model_class.primary_key => ids})
      models.each do |model|
        puts "ActsAsXapian::WriteableIndex.rebuild_index #{model_class} #{model.id}" if verbose
        model.xapian_index
      end
    end
  end

  @@writable_db.flush

  # Rename into place
  old_path = self.db_path
  temp_path = "#{old_path}.tmp"
  if File.exist?(temp_path)
    raise "temporary database found #{temp_path} which is not Xapian flint database, please delete for me" unless File.exist?(File.join(temp_path, "iamflint"))
    FileUtils.rm_r(temp_path)
  end
  FileUtils.mv(old_path, temp_path) if File.exist?(old_path)
  FileUtils.mv(new_path, old_path)

  # Delete old database
  if File.exist?(temp_path)
    raise "old database now at #{temp_path} is not Xapian flint database, please delete for me" unless File.exist?(File.join(temp_path, "iamflint"))
    FileUtils.rm_r(temp_path)
  end

  ActsAsXapianJob.delete_all ['id <= ?', most_recent_job.id] if most_recent_job

  # You'll want to restart your FastCGI or Mongrel processes after this,
  # so they get the new db
end

.replace_document(*args) ⇒ Object



13
14
15
# File 'lib/acts_as_xapian/writeable_index.rb', line 13

def replace_document(*args)
  @@writable_db.replace_document(*args)
end

.update_index(flush = false, verbose = false) ⇒ Object

Update index with any changes needed, call this offline. Only call it from a script that exits - otherwise Xapian’s writable database won’t flush your changes. Specifying flush will reduce performance, but make sure that each index update is definitely saved to disk before logging in the database that it has been.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/acts_as_xapian/writeable_index.rb', line 46

def update_index(flush = false, verbose = false)
  # puts "start of self.update_index" if verbose

  # Before calling writable_init we have to make sure every model class has been initialized.
  # i.e. has had its class code loaded, so acts_as_xapian has been called inside it, and
  # we have the info from acts_as_xapian.
  model_classes = ActsAsXapianJob.find(:all, :select => 'model', :group => 'model').map {|a| a.model.constantize }
  # If there are no models in the queue, then nothing to do
  return if model_classes.empty?

  self.writable_init

  ids_to_refresh = ActsAsXapianJob.find(:all, :select => 'id').map { |i| i.id }
  ids_to_refresh.each do |id|
    begin
      ActsAsXapianJob.transaction do
        job = ActsAsXapianJob.find(id, :lock =>true)
        puts "ActsAsXapian::WriteableIndex.update_index #{job.action} #{job.model} #{job.model_id.to_s}" if verbose
        begin
          case job.action
          when 'update'
            # XXX Index functions may reference other models, so we could eager load here too?
            model = job.model.constantize.find(job.model_id) # :include => cls.constantize.xapian_options[:include]
            model.xapian_index
          when 'destroy'
            # Make dummy model with right id, just for destruction
            model = job.model.constantize.new
            model.id = job.model_id
            model.xapian_destroy
          else
            raise "unknown ActsAsXapianJob action '#{job.action}'"
          end
        rescue ActiveRecord::RecordNotFound => e
          job.action = 'destroy'
          retry
        end
        job.destroy

        @@writable_db.flush if flush
      end
    rescue => detail
      # print any error, and carry on so other things are indexed
      # XXX If item is later deleted, this should give up, and it
      # won't. It will keep trying (assuming update_index called from
      # regular cron job) and mayhap cause trouble.
      STDERR.puts("#{detail.backtrace.join("\n")}\nFAILED ActsAsXapian::WriteableIndex.update_index job #{id} #{$!}")
    end
  end
end

.writable_init(suffix = "") ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/acts_as_xapian/writeable_index.rb', line 17

def writable_init(suffix = "")
  raise NoXapianRubyBindingsError.new("Xapian Ruby bindings not installed") unless ActsAsXapian.bindings_available
  raise "acts_as_xapian hasn't been called in any models" if @@init_values.empty?

  # if DB is not nil, then we're already initialised, so don't do it again
  return unless @@writable_db.nil?

  prepare_environment

  new_path = @@db_path + suffix
  raise "writable_suffix/suffix inconsistency" if @@writable_suffix && @@writable_suffix != suffix

  # for indexing
  @@writable_db = Xapian::WritableDatabase.new(new_path, Xapian::DB_CREATE_OR_OPEN)
  @@term_generator = Xapian::TermGenerator.new()
  @@term_generator.set_flags(Xapian::TermGenerator::FLAG_SPELLING, 0)
  @@term_generator.database = @@writable_db
  @@term_generator.stemmer = @@stemmer
  @@writable_suffix = suffix
end