moving zebra to zebra-broken
[zcc.git] / lib / zcc / query.rb
blobf616d44970ca511305f8ba454fecf54a9f8f6753
1 module ZCC
3   class Query
4     attr_reader :term, :zservers, :type, :zsearch
5     
6     def initialize(term, zservers)
7       self.term = term #=> string of original query post any filtering
8       #puts "term: " + self.term
9       @zservers = []
10       @zservers << zservers #=>array of Zserver objects
11       self.zservers.compact!
12       self.zservers.flatten!
13       #puts self.inspect
14       #puts self.zservers[0].to_s
15     end
16     
17     def term=(term)
18       if term =~ /\d+-\d+/
19         @term = lccn_conversion(term)
20         @type = 'lccn'
21         #puts "converted lccn to #{@term}"
22         @zsearch = "@attr 1=9 #{@term}"
23       elsif term =~ /\d+[X||\d]/ and !(term.match( /[a-wyz]/i ))
24         #puts "Searching for ISBN: #{term}"
25         @term = term
26         @type = 'isbn'
27         @zsearch = "@attr 1=7 #{@term}"
28       else ( term[/[a-z]/i] ) #-- This check for a string could be better!
29         #puts "searching for title:#{term}"
30         if term.match(/ :au /)
31           term = term.split(" :au ")
32           puts "Author and title then"
33           @term = term[0]
34           @zsearch = "@and @attr 1=4 \"#{@term}\" @attr 1=1 \"#{term[1]} \"" #was: "@attr 1=4 \"'#{@term}'\""
35         else
36           @term = term
37           @zsearch = "@attr 1=4 \"#{@term}\"" #was: "@attr 1=4 \"'#{@term}'\""
38         end
39         @type = 'title'
40         
41         #puts self.term
42         #puts self.type
43         #puts self.zsearch
44         
45       end
46     end
47     
48     #This is the main search logic of the whole shebang. Aliased as 'search'.
49     #The method on a query object and is passed the number of records from each host to present to the user. Default number of records is 10.
50     #Still need to work on a way to get options in so this might change.
51     #Currently only MARC21 is supported.
52     def zoom(show = 10)
53       result_set = ResultSet.new(self)
54       #puts zservers.inspect
55       search_threads=[]
56       puts zservers.size.to_s + " z-servers in group " + zservers[0].group.to_s
57       z = 0
58       self.zservers.each do |server|
59           z += 1
60           #zservers are made up of...
61           #puts server.to_s
62         search_threads << Thread.new(server, z) do |myserver, myz|
63           begin
64             conn = ZOOM::Connection.new
65             conn.connect(myserver.host, server.port) #do |conn 
66             conn.set_option('charset', 'UTF-8')
67             conn.preferred_record_syntax = 'MARC21'
68             conn.database_name = myserver.database
69             puts "#{myz} Searching:          #{myserver.to_s} | #{self.zsearch}"
70             rset = conn.search(self.zsearch)
71             say(myz.to_s.headline + " Finished searching: #{myserver.to_s} | rset.size: " + "#{rset.size}".red.bold)
72             rset_recs = rset[0, show]
73             #puts "rset_recs in query.search: " 
74             #puts rset_recs
75             i = 0
76             rset_recs.each do |rec|
77               #puts myserver.to_s
78               #puts rec
79               #puts
80               marc_record = ZCC.convert_char(rec)
81               #puts "gets past character conversion"
82               #puts "-------------\n"
83               #puts marc_record
84               #puts "---------------\n"
85               zcc_record = Record.new(marc_record, myserver)
86               result_set.ingest(zcc_record)
87               puts "#{myz} record #{i} from       #{myserver}..."
88               i += 1
89             end
90           rescue Exception => e
91             zerror_log("dead thread: " + myserver.to_s + " | " + e)
92             puts "\a#{myz}!!!!!!!! Thread died #{myserver} !!!!!"
93           end
94           #puts "end: #{Thread.list}"
95           #puts "Results processed from:  #{myserver.to_s}"
96         end
97         
98       end
99       search_threads.each{|thread| thread.join}
100       return result_set
101     end
102     
103     alias search zoom
104     def zerror_log error
105       File.open("#{File.expand_path("~")}/.zcc/zerror_log", "a+") do |f|
106         f.write error + "\n"
107       end
108     end
109     
110   end
111   
112   
113   def convert_char rsetrec
114     rec = MARC::Record.new_from_marc(rsetrec.raw)
115     #puts "initial rec" + rec.to_s
116     ldr9 = rec.leader[9, 1]
117     return_rec = ''
118     #puts "gets to creating dummy record."
119     if ldr9 == ' '
120       #return_rec = MARC::Record.new_from_marc(rsetrec.raw('MARC-8', 'UTF-8')) #This does NOT work
121       return_rec = MARC::XMLReader.new(StringIO.new(rsetrec.xml('MARC-8', 'UTF-8'))).to_a
122       return_rec = return_rec[0]
123       return_rec.leader[9,1] = 'a'
124       #puts "return_rec" + return_rec.to_s
125     elsif ldr9 == 'a'
126       #puts "already unicode"
127       return_rec = rec
128     else
129       raise "Invalid value in leader 9 for MARC21"
130     end
131     return_rec
132   end
133   
134   def lccn_conversion lccn
135     split_lccn = lccn.split('-')
136     year_len = split_lccn[0].length
137     serial_len = split_lccn[1].length
138     start_length = year_len + serial_len
139     if year_len == 2
140       final_lccn = lccn.gsub('-', "#{'0' * (8 - start_length)}")
141     elsif year_len == 4
142       final_lccn = lccn.gsub('-', "#{'0' * (10 - start_length)}")
143     end
144     final_lccn
145   end
146   
147     #++ compare() is a method to print out a comparison of two MARC records tag by tag (not by subfields).
148   # A match between lines is denoted with a 'm'. If there are differences between the records,
149   # the object that recieves the compare call is denoted with a '+' and the object passed in 
150   # parens is denoted with '-'. 
151   def compare_marc(orig, rec2)
152     puts "+ = #{orig.zserver.to_s}".bold
153     puts "- = #{rec2.zserver.to_s}".bold
154     orig = orig.marc
155     rec2 = rec2.marc
156     
157     for ft in ('000'..'999')
158       fields_original = orig.find_all {|f| f.tag == ft}
159       fields_record2 = rec2.find_all {|f| f.tag == ft}
160       fields_orig = []
161       fields_rec2 = []
163       fields_original.each {|f| fields_orig << f.to_s}
164       fields_record2.each {|f| fields_rec2 << f.to_s}
166       matches = fields_orig & fields_rec2
167       matches.each { |f| puts "m".bold + " #{f}" } if matches                   
169       fields_orig -= matches
170       fields_orig.each {|f| puts "+".bold + " #{f}"} if fields_orig
172       fields_rec2 -= matches
173       fields_rec2.each {|f| puts "-".bold + " #{f}"} if fields_rec2
174     end
175   end
176   
177   def blank_field_prompt(field, subfield)
178     #puts "subfield: #{subfield}"
179     value = ask("\nYour MARC record does not contain #{field}#{subfield}.\nEnter the intended value for #{field}#{subfield} or hit ENTER to leave blank\n#{field}#{subfield} > ")
180     value
181   end
182