3 class Seeker < ActiveXML::Base
5 len = data.children.length
6 #if len == 1 and data.children[0].kind_of? XML::Smart::Dom::Text
12 def self.prepare_result(query, baseproject=nil, project=nil, exclude_filter=nil, exclude_debug=false)
14 cache_key += "_#{baseproject}" if baseproject
15 cache_key += "_#{exclude_filter}" if exclude_filter
16 cache_key += "_#{exclude_debug}" if exclude_debug
17 cache_key += "_#{project}" if project
18 cache_key = 'searchresult_' + MD5::md5( cache_key ).to_s
19 Rails.cache.fetch(cache_key, :expires_in => 10.minutes) do
20 SearchResult.search(query, baseproject, project, exclude_filter, exclude_debug)
25 class SearchResult < Array
26 def self.search(query, baseproject, project=nil, exclude_filter=nil, exclude_debug=false)
28 words = query.split(" ").select {|part| !part.match(/^[0-9_\.-]+$/) }
29 versions = query.split(" ").select {|part| part.match(/^[0-9_\.-]+$/) }
30 logger.debug "splitted words and version: #{words.inspect} #{versions.inspect} "
31 raise "Please provide a valid search term" if words.blank?
33 xpath = "contains-ic(@name, " + words.map{|word| "'#{word}'"}.join(", ") + ")"
34 xpath += ' and ' + versions.map {|part| "starts-with(@version,'#{part}')"}.join(" and ") unless versions.blank?
35 xpath += " and path/project='#{baseproject}'" unless baseproject.blank?
36 xpath += " and @project = '#{project}' " unless project.blank?
37 xpath += " and not(contains-ic(@name, '-debuginfo')) and not(contains-ic(@name, '-debugsource'))" if exclude_debug
38 #xpath += " and not(contains-ic(@project, '#{exclude_filter}'))" unless exclude_filter.blank?
40 bin = Seeker.find :binary, :match => xpath
41 pat = Seeker.find :pattern, :match => xpath
43 result.add_patlist(pat)
44 result.add_binlist(bin)
46 # remove this hack when the backend can filter for project names
47 result.reject!{|res| /#{exclude_filter}/.match( res.project ) } unless exclude_filter.blank?
49 result.sort! {|x,y| y.relevance <=> x.relevance}
59 "<Seeker::Searchresult ##{object_id} @length=#{size}>"
67 attr_reader :binary_count
68 attr_reader :pattern_count
69 attr_accessor :page_length
79 # page index starts with 1
81 return [] if idx > page_count
82 page = self[@page_length*(idx-1),@page_length]
84 item.update_description
90 #logger.debug "[SearchResult] calculating page_count: self.length: #{self.length}, @page_length: #@page_length"
91 ((self.length-1)/@page_length)+1
94 def add_binlist(binlist)
97 binlist.each_binary do |bin|
99 fragment = Fragment.new(bin)
100 fragment.fragment_type = :binary
101 add_fragment(fragment)
105 def add_patlist(patlist)
108 patlist.each_pattern do |pat|
110 fragment = Fragment.new(pat)
111 fragment.fragment_type = :pattern
112 add_fragment(fragment)
116 def add_fragment(fragment)
118 if @index.has_key? key
121 case fragment.fragment_type
123 item = Binary.new(key, @query)
125 item = Pattern.new(key, @query)
130 item.add_entry(fragment)
137 out << "<li>#{item.key} #{item.dump}</li>"
147 attr_reader :repository
148 attr_reader :ymp_link
149 attr_reader :description
150 attr_reader :short_description
151 attr_reader :relevance
153 def initialize(key, query)
164 "<#{self.class.name} ##{object_id} @length=#{size}>"
167 def add_entry(element)
168 if element.__key != @key
169 raise "key mismatch: #{element.__key} != #@key"
172 cache_data(element) unless @data_cached
173 calculate_relevance unless @relevance_calculated
177 out = "<ul><li><b>Relevance:</b> #@relevance</li>"
179 out << "<li>#{bin.filename} #{bin.dump}</li>"
185 def update_description
186 # implement in derived classes
191 def cache_data(element)
192 @project = element.project
193 @repository = element.repository
195 cache_specific_data(element)
199 def cache_specific_data(element)
200 # implement in derived classes
203 def calculate_relevance
204 quoted_query = Regexp.quote @query
205 @relevance_calculated = true
206 @relevance += 15 if name =~/^#{quoted_query}$/i
207 @relevance += 5 if name =~/^#{quoted_query}/i
208 @relevance += 15 if project =~ /^openSUSE:/i
209 @relevance += 5 if project =~ /^#{quoted_query}$/i
210 @relevance += 2 if project =~ /^#{quoted_query}/i
211 @relevance -= 5 if project =~ /unstable/i
212 @relevance -= 10 if project =~ /^home:/
213 calculate_specific_relevance
216 def calculate_specific_relevance
217 # implement in derived classes
222 attr_reader :arch_hash
223 attr_reader :filename
226 def initialize(key, query)
228 @arch_hash = Hash.new
231 def cache_specific_data(element)
232 @filename = element.filename
236 def calculate_specific_relevance
237 @relevance -= 10 if name =~ /-debugsource$/
238 @relevance -= 10 if name =~ /-debuginfo$/
239 @relevance -= 3 if name =~ /-devel$/
240 @relevance -= 3 if name =~ /-doc$/
243 def update_description
244 @description = cache.description(self)
245 return unless @description.nil?
246 return if self.empty?
249 info = ::Published.find bin.filename, :view => "fileinfo", :project => @project,
250 :repository => @repository, :arch => bin.arch.to_s
251 if info.has_element? :description
252 @description = info.description.to_s
256 rescue ActiveXML::Transport::NotFoundError
260 cache.store_description(self, @description)
266 attr_reader :filename
267 attr_reader :filepath
268 attr_reader :repository
271 def calculate_specific_relevance
276 def cache_specific_data(element)
277 @filename = element.filename.to_s
278 @filepath = element.filepath.to_s
279 @repository = element.repository.to_s
280 @type = element.type.to_s
283 def update_description
284 @description = cache.description(self)
285 return unless @description.nil?
287 pat = ::Published.find @filename, :project => @project, :repository => @repository, :view => :fileinfo
288 if pat.has_element? :description
289 @description = pat.description.to_s
296 cache.store_description(self, @description)
301 class Fragment < Hash
302 attr_accessor :fragment_type
304 def initialize(element)
305 #XXX: xml-backend specific code, change when xml-backends are
307 element.data.attributes.each do |attr|
308 self[attr.name] = attr.value
313 @__key ||= @fragment_type.to_s+"|"+%w(project repository name).map{|x| self[x]}.join('|')
319 out << "<li><b>#{key}:</b> #{val}</li>x"
326 method_missing(:type,*args)
329 def method_missing(symbol,*args,&block)
330 if self.has_key? symbol.to_s
331 return self[symbol.to_s]
333 super(symbol,*args,&block)
338 attr_accessor :active
340 tmpdir = RAILS_ROOT+"/tmp/cache"
341 @desctmpdir = tmpdir+"/_descriptions"
342 @resulttmpdir = tmpdir+"/_searchresults"
344 @resultexpire = 300.0
352 def description(item)
353 fname = @desctmpdir + "/" + Digest::MD5.hexdigest(item.key)
354 return read(fname, @descexpire)
357 def searchresult(query, baseproject)
358 key = query + "|" + baseproject.to_s
359 fname = @resulttmpdir + "/" + Digest::MD5.hexdigest(key)
360 if str = read(fname, @resultexpire)
361 return Marshal.load(str)
367 def store_description(item, desc)
368 fname = @desctmpdir + "/" + Digest::MD5.hexdigest(item.key)
372 def store_searchresult(query, baseproject, result)
373 key = query + "|" + baseproject.to_s
374 fname = @resulttmpdir + "/" + Digest::MD5.hexdigest(key)
375 write(fname, Marshal.dump(result))
379 def read(fname, exp_time_sec)
380 return nil unless @active
382 stat = File::stat(fname)
387 if (Time.now-stat.mtime) < exp_time_sec
388 logger.debug "[Seeker::SearchResult::Cache] reading #{fname} from cache"
389 return File.read(fname)
395 def write(fname, data)
396 return unless @active
397 File.open(fname,"w") do |f|
398 logger.debug "[Seeker::SearchResult::Cache] writing #{fname} to cache"