C0 code coverage information
Generated on Tue Oct 16 11:40:50 -0400 2007 with rcov 0.8.0
Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
1 # Copyright (C) 2004-2006 Laurent Sansonetti
2 #
3 # Alexandria is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License as
5 # published by the Free Software Foundation; either version 2 of the
6 # License, or (at your option) any later version.
7 #
8 # Alexandria is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public
14 # License along with Alexandria; see the file COPYING. If not,
15 # write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 # Boston, MA 02111-1307, USA.
17
18 require 'gdk_pixbuf2'
19
20 module Alexandria
21
22 class DuplicateBookException < NameError
23
24 end
25
26 module UI
27 class KeepBadISBNDialog < AlertDialog
28 include GetText
29 GetText.bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")
30
31 def initialize(parent, book)
32 super(parent, _("Invalid ISBN '%s'") % book.isbn,
33 Gtk::Stock::DIALOG_QUESTION,
34 [[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
35 [_("_Keep"), Gtk::Dialog::RESPONSE_OK]],
36 _("The book titled '%s' has an invalid ISBN, but still " +
37 "exists in the providers libraries. Do you want to " +
38 "keep the book but change the ISBN or cancel the add?") \
39 % book.title)
40 self.default_response = Gtk::Dialog::RESPONSE_OK
41 show_all and @response = run
42 destroy
43 end
44
45 def keep?
46 @response == Gtk::Dialog::RESPONSE_OK
47 end
48 end
49
50 class NewBookDialog < GladeBase
51 include GetText
52 extend GetText
53 GetText.bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")
54
55 def initialize(parent, selected_library=nil, &block)
56 super('new_book_dialog.glade')
57 puts "New Book Dialog" if $DEBUG
58 @new_book_dialog.transient_for = @parent = parent
59 @block = block
60 @destroyed = false
61 libraries = Libraries.instance.all_regular_libraries
62 if selected_library.is_a?(SmartLibrary)
63 selected_library = libraries.first
64 end
65 @combo_libraries.populate_with_libraries(libraries,
66 selected_library)
67
68 @treeview_results.model = Gtk::ListStore.new(String, String,
69 Gdk::Pixbuf)
70 @treeview_results.selection.mode = Gtk::SELECTION_MULTIPLE
71 @treeview_results.selection.signal_connect('changed') do
72 @button_add.sensitive = true
73 end
74
75 renderer = Gtk::CellRendererPixbuf.new
76 col = Gtk::TreeViewColumn.new("", renderer)
77 col.set_cell_data_func(renderer) do |column, cell, model, iter|
78 pixbuf = iter[2]
79 max_height = 25
80
81 if pixbuf.height > max_height
82 new_width = pixbuf.width * (max_height.to_f / pixbuf.height)
83 pixbuf = pixbuf.scale(new_width, max_height)
84 end
85
86 cell.pixbuf = pixbuf
87 end
88 @treeview_results.append_column(col)
89
90 col = Gtk::TreeViewColumn.new("", Gtk::CellRendererText.new,
91 :text => 0)
92 @treeview_results.append_column(col)
93
94 @combo_search.active = 0
95
96 # Re-select the last selected criterion.
97 begin
98 @title_radiobutton.active = @@last_criterion_was_not_isbn
99 rescue NameError
100 @@last_criterion_was_not_isbn = false
101 end
102
103 if @@last_criterion_was_not_isbn
104 @entry_search.grab_focus
105 else
106 @entry_isbn.grab_focus
107 end
108
109 @find_thread = nil
110 @image_thread = nil
111
112 @new_book_dialog.signal_connect("destroy") {
113 @new_book_dialog.destroy
114 @destroyed = true
115 }
116 end
117
118 def on_criterion_toggled(item)
119 return unless item.active?
120 if is_isbn = item == @isbn_radiobutton
121 @latest_size = @new_book_dialog.size
122 @new_book_dialog.resizable = false
123 else
124 @new_book_dialog.resizable = true
125 @new_book_dialog.resize(*@latest_size) unless @latest_size.nil?
126 end
127 @entry_isbn.sensitive = is_isbn
128 @combo_search.sensitive = !is_isbn
129 @entry_search.sensitive = !is_isbn
130 @button_find.sensitive = !is_isbn
131 @scrolledwindow.visible = !is_isbn
132 on_changed(is_isbn ? @entry_isbn : @entry_search)
133 unless is_isbn
134 @button_add.sensitive =
135 @treeview_results.selection.count_selected_rows > 0
136 end
137
138 # Remember the last criterion selected (so that we can re-select
139 # it when the dialog opens again).
140 @@last_criterion_was_not_isbn = !is_isbn
141 end
142
143 def on_changed(entry)
144 ok = !entry.text.strip.empty?
145 decode_cuecat?(@entry_isbn) if entry == @entry_isbn
146 (entry == @entry_isbn ? @button_add : @button_find).sensitive = ok
147 end
148
149 def image_error_dialog(error)
150 ErrorDialog.new(
151 @parent,
152 _("A problem occurred while downloading images"),
153 error)
154 end
155
156 def get_images_async
157 puts "get_images_async" if $DEBUG
158 @images = {}
159 @image_error = nil
160 @image_thread = Thread.new do
161 puts "New @image_thread #{Thread.current}" if $DEBUG
162 begin
163 @results.each_with_index do |result, i|
164 uri = result[1]
165 if uri
166 if URI.parse(uri).scheme.nil?
167 File.open(uri, "r") do |io|
168 @images[i] = io.read
169 end
170 else
171 @images[i] = URI.parse(uri).read
172 end
173 end
174 end
175 rescue => e
176 @image_error = e.message
177 end
178 end
179
180 Gtk.timeout_add(100) do
181 if @image_error
182 image_error_dialog(@image_error)
183 else
184 @images.each_pair do |key, value|
185 begin
186 loader = Gdk::PixbufLoader.new
187 loader.last_write(value)
188 pixbuf = loader.pixbuf
189
190 if pixbuf.width > 1
191 iter = @treeview_results.model.get_iter(key.to_s)
192 unless @treeview_results.model.iter_is_valid?(iter)
193 raise "Iter is invalid! %s" % iter
194 end
195 iter[2] = pixbuf #I bet you this is it!
196 end
197
198 @images.delete(key)
199 rescue => e
200 image_error_dialog(e.message)
201 end
202 end
203 end
204
205 # Stop if the image download thread has stopped.
206 if @image_thread.alive?
207 puts "@image_thread (#{@image_thread}) still alive." if $DEBUG
208 true
209 else
210 puts "@image_thread (#{@image_thread}) asleep now." if $DEBUG
211 false
212 end
213 end
214 end
215
216 def on_find
217 puts "on_find" if $DEBUG
218 mode = case @combo_search.active
219 when 0
220 BookProviders::SEARCH_BY_TITLE
221 when 1
222 BookProviders::SEARCH_BY_AUTHORS
223 when 2
224 BookProviders::SEARCH_BY_KEYWORD
225 end
226
227 criterion = @entry_search.text.strip
228 @treeview_results.model.clear
229 puts "TreeStore Model: %s columns; ref_counts: %s" %
230 [@treeview_results.model.n_columns, @treeview_results.model.ref_count] if $DEBUG
231 @new_book_dialog.sensitive = false
232 @find_error = nil
233 @results = nil
234
235 @find_thread.kill if @find_thread
236 @image_thread.kill if @image_thread
237
238 @find_thread = Thread.new do
239 puts "New @find_thread #{Thread.current}" if $DEBUG
240 begin
241 @results = Alexandria::BookProviders.search(criterion, mode)
242 puts "got #{@results.length} results" if $DEBUG
243 rescue => e
244 @find_error = e.message
245 end
246 end
247
248 Gtk.timeout_add(100) do
249 # This block copies results into the tree view, or shows an
250 # error if the search failed.
251
252 # Err... continue == false if @find_error
253 continue = if @find_error
254 ErrorDialog.new(@parent,
255 _("Unable to find matches for your search"),
256 @find_error)
257 false
258 elsif @results
259 puts "Got results: #{@results[0]}..." if $DEBUG
260 @results.each do |book, cover|
261 s = _("%s, by %s") % [ book.title,
262 book.authors.join(', ') ]
263 if @results.find { |book2, cover2|
264 book.title == book2.title and
265 book.authors == book2.authors
266 }.length > 1
267 s += " (#{book.edition}, #{book.publisher})"
268 end
269 puts "Copying %s into tree view." % book.title if $DEBUG
270 iter = @treeview_results.model.append
271 iter[0] = s
272 iter[1] = book.ident
273 iter[2] = Icons::BOOK
274 end
275
276 # Kick off the image download thread.
277 if @find_thread.alive?
278 puts "@find_thread (#{@find_thread}) still alive." if $DEBUG
279 true
280 else
281 puts "@find_thread (#{@find_thread}) asleep now." if $DEBUG
282 #Not really async now.
283 get_images_async
284 false #continue == false if you get to here. Stop timeout_add.
285 end
286 else
287 # Stop if the book find thread has stopped.
288 @find_thread.alive?
289 end
290 # continue == false if @find_error OR if results are returned
291 # @new_book_dialog.sensitive is a bad call if window has been destroyed
292 # timeout_add ends if continue is false!
293
294 unless continue
295 unless @find_thread.alive? #This happens after find_thread is done
296 unless @destroyed
297 @new_book_dialog.sensitive = true
298 @button_add.sensitive = false
299 end
300 end
301 end
302
303 continue #timeout_add loop condition
304 end
305 end
306
307 def decode_cuecat?(entry)
308 if entry.text=~/^\..*?\..*?\.(.*?)\.$/
309 tmp = $1.tr('a-zA-Z0-9+-', ' -_')
310 tmp = ((32 + tmp.length * 3/4).to_i.chr << tmp).unpack('u')[0]
311 tmp.chomp!("\000")
312 entry.text = tmp.gsub!(/./) {|c| (c[0] ^ 67).chr }
313 if entry.text.count('^ -~') > 0
314 entry.text = 'Bad scan result'
315 end
316 end
317 end
318
319 def on_results_button_press_event(widget, event)
320 # double left click
321 if event.event_type == Gdk::Event::BUTTON2_PRESS and
322 event.button == 1
323
324 on_add
325 end
326 end
327
328 def on_add
329 return unless @button_add.sensitive?
330 @find_thread.kill if @find_thread
331 @image_thread.kill if @image_thread
332
333 begin
334 libraries = Libraries.instance.all_libraries
335 library, new_library =
336 @combo_libraries.selection_from_libraries(libraries)
337 books_to_add = []
338 if @isbn_radiobutton.active?
339 # Perform the ISBN search via the providers.
340 isbn = begin
341 Library.canonicalise_isbn(@entry_isbn.text)
342 rescue
343 raise _("Couldn't validate the EAN/ISBN you " +
344 "provided. Make sure it is written " +
345 "correctly, and try again.")
346 end
347 assert_not_exist(library, @entry_isbn.text)
348 books_to_add << Alexandria::BookProviders.isbn_search(isbn)
349 else
350 @treeview_results.selection.selected_each do |model, path,
351 iter|
352 @results.each do |book, cover|
353 next unless book.ident == iter[1]
354 #print iter[0].inspect
355 #print " "
356 #puts iter[1].inspect
357 begin
358 next unless
359 assert_not_exist(library, book.isbn)
360 rescue Alexandria::Library::InvalidISBNError
361 next unless
362 KeepBadISBNDialog.new(@parent, book).keep?
363 book.isbn = book.saved_ident = nil
364 rescue Alexandria::Library::NoISBNError
365 book.isbn = book.saved_ident = nil
366 books_to_add << [book, cover]
367 next
368 end
369 books_to_add << [book, cover]
370
371 end
372 end
373 end
374
375 # Save the books in the library.
376 books_to_add.each do |book, cover_uri|
377 unless cover_uri.nil?
378 library.save_cover(book, cover_uri)
379 end
380 library << book
381 library.save(book)
382 end
383
384 # Do not destroy if there is no addition.
385 return if books_to_add.empty?
386
387 # Now we can destroy the dialog and go back to the main
388 # application.
389 @new_book_dialog.destroy
390 @block.call(books_to_add.map { |x| x.first },
391 library,
392 new_library)
393 rescue => e
394 ErrorDialog.new(@parent, _("Couldn't add the book"), e.message)
395 end
396 end
397
398 def on_cancel
399 @find_thread.kill if @find_thread
400 @image_thread.kill if @image_thread
401 @new_book_dialog.destroy
402 end
403
404 def on_focus
405 if @isbn_radiobutton.active? and @entry_isbn.text.strip.empty?
406 clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
407 if text = clipboard.wait_for_text
408 @entry_isbn.text = text if
409 Library.valid_isbn?(text) or Library.valid_ean?(text) or
410 Library.valid_upc?(text)
411 end
412 end
413 end
414
415 def on_clicked(widget, event)
416 if event.event_type == Gdk::Event::BUTTON_PRESS and
417 event.button == 1
418
419 radio, target_widget, box2, box3 = case widget
420 when @eventbox_entry_search
421 [@title_radiobutton, @entry_search,
422 @eventbox_combo_search, @eventbox_entry_isbn]
423
424 when @eventbox_combo_search
425 [@title_radiobutton, @combo_search,
426 @eventbox_entry_search, @eventbox_entry_isbn]
427
428 when @eventbox_entry_isbn
429 [@isbn_radiobutton, @entry_isbn,
430 @eventbox_entry_search, @eventbox_combo_search]
431 end
432 radio.active = true
433 target_widget.grab_focus
434 widget.above_child = false
435 box2.above_child = box3.above_child = true
436 end
437 end
438
439 def on_help
440 begin
441 Gnome::Help.display('alexandria', 'add-book-by-isbn')
442 rescue => e
443 ErrorDialog.new(@preferences_dialog, e.message)
444 end
445 end
446
447 #######
448 private
449 #######
450
451 def assert_not_exist(library, isbn)
452 # Check that the book doesn't already exist in the library.
453 canonical = Library.canonicalise_isbn(isbn)
454 if book = library.find { |book| book.isbn == canonical }
455 raise DuplicateBookException, _("'%s' already exists in '%s' (titled '%s').") % \
456 [ isbn, library.name, book.title.sub("&", "&") ]
457 end
458 true
459 end
460 end
461 end
462 end
Generated using the rcov code coverage analysis tool for Ruby version 0.8.0.