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 project =~ /^#{quoted_query}$/i
208 @relevance += 5 if name =~ /#{quoted_query}/i
209 @relevance += 2 if project =~ /#{quoted_query}/i
210 @relevance += 1 if project =~ /^openSUSE/i
212 @relevance -= 10 if project =~ /^home:/
214 calculate_specific_relevance
217 def calculate_specific_relevance
218 # implement in derived classes
223 attr_reader :arch_hash
224 attr_reader :filename
227 def initialize(key, query)
229 @arch_hash = Hash.new
232 def cache_specific_data(element)
233 @filename = element.filename
237 def calculate_specific_relevance
238 @relevance -= 10 if name =~ /-debugsource$/
239 @relevance -= 10 if name =~ /-debuginfo$/
240 @relevance -= 3 if name =~ /-devel$/
241 @relevance -= 3 if name =~ /-doc$/
244 def update_description
245 @description = cache.description(self)
246 return unless @description.nil?
247 return if self.empty?
250 info = ::Published.find bin.filename, :view => "fileinfo", :project => @project,
251 :repository => @repository, :arch => bin.arch.to_s
252 if info.has_element? :description
253 @description = info.description.to_s
257 rescue ActiveXML::Transport::NotFoundError
261 cache.store_description(self, @description)
267 attr_reader :filename
268 attr_reader :filepath
269 attr_reader :repository
272 def calculate_specific_relevance
277 def cache_specific_data(element)
278 @filename = element.filename.to_s
279 @filepath = element.filepath.to_s
280 @repository = element.repository.to_s
281 @type = element.type.to_s
284 def update_description
285 @description = cache.description(self)
286 return unless @description.nil?
288 pat = ::Published.find @filename, :project => @project, :repository => @repository, :view => :fileinfo
289 if pat.has_element? :description
290 @description = pat.description.to_s
297 cache.store_description(self, @description)
302 class Fragment < Hash
303 attr_accessor :fragment_type
305 def initialize(element)
306 #XXX: xml-backend specific code, change when xml-backends are
308 element.data.attributes.each do |attr|
309 self[attr.name] = attr.value
314 @__key ||= @fragment_type.to_s+"|"+%w(project repository name).map{|x| self[x]}.join('|')
320 out << "<li><b>#{key}:</b> #{val}</li>x"
327 method_missing(:type,*args)
330 def method_missing(symbol,*args,&block)
331 if self.has_key? symbol.to_s
332 return self[symbol.to_s]
334 super(symbol,*args,&block)
339 attr_accessor :active
341 tmpdir = RAILS_ROOT+"/tmp/cache"
342 @desctmpdir = tmpdir+"/_descriptions"
343 @resulttmpdir = tmpdir+"/_searchresults"
345 @resultexpire = 300.0
353 def description(item)
354 fname = @desctmpdir + "/" + Digest::MD5.hexdigest(item.key)
355 return read(fname, @descexpire)
358 def searchresult(query, baseproject)
359 key = query + "|" + baseproject.to_s
360 fname = @resulttmpdir + "/" + Digest::MD5.hexdigest(key)
361 if str = read(fname, @resultexpire)
362 return Marshal.load(str)
368 def store_description(item, desc)
369 fname = @desctmpdir + "/" + Digest::MD5.hexdigest(item.key)
373 def store_searchresult(query, baseproject, result)
374 key = query + "|" + baseproject.to_s
375 fname = @resulttmpdir + "/" + Digest::MD5.hexdigest(key)
376 write(fname, Marshal.dump(result))
380 def read(fname, exp_time_sec)
381 return nil unless @active
383 stat = File::stat(fname)
388 if (Time.now-stat.mtime) < exp_time_sec
389 logger.debug "[Seeker::SearchResult::Cache] reading #{fname} from cache"
390 return File.read(fname)
396 def write(fname, data)
397 return unless @active
398 File.open(fname,"w") do |f|
399 logger.debug "[Seeker::SearchResult::Cache] writing #{fname} to cache"