alexandria-book-collection-manager 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.reek +3 -0
  4. data/.rubocop.yml +8 -0
  5. data/.rubocop_todo.yml +20 -23
  6. data/.simplecov +3 -0
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +6 -0
  9. data/Gemfile +3 -2
  10. data/INSTALL.rdoc +0 -8
  11. data/README.md +72 -0
  12. data/Rakefile +4 -2
  13. data/TODO.md +26 -0
  14. data/alexandria-book-collection-manager.gemspec +6 -3
  15. data/bin/alexandria +1 -6
  16. data/dogtail/basic_run_test.py +9 -0
  17. data/lib/alexandria/book_providers.rb +2 -6
  18. data/lib/alexandria/book_providers/proxis.rb +1 -1
  19. data/lib/alexandria/book_providers/web.rb +1 -1
  20. data/lib/alexandria/book_providers/z3950.rb +4 -4
  21. data/lib/alexandria/export_library.rb +20 -27
  22. data/lib/alexandria/import_library.rb +7 -5
  23. data/lib/alexandria/models/book.rb +11 -1
  24. data/lib/alexandria/models/library.rb +4 -17
  25. data/lib/alexandria/preferences.rb +5 -5
  26. data/lib/alexandria/smart_library.rb +2 -8
  27. data/lib/alexandria/ui/callbacks.rb +1 -1
  28. data/lib/alexandria/ui/columns.rb +9 -0
  29. data/lib/alexandria/ui/completion_models.rb +1 -29
  30. data/lib/alexandria/ui/dialogs/acquire_dialog.rb +2 -3
  31. data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +1 -2
  32. data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +3 -2
  33. data/lib/alexandria/ui/iconview.rb +1 -6
  34. data/lib/alexandria/ui/libraries_combo.rb +1 -0
  35. data/lib/alexandria/ui/listview.rb +5 -10
  36. data/lib/alexandria/ui/main_app.rb +0 -1
  37. data/lib/alexandria/ui/sidepane.rb +1 -1
  38. data/lib/alexandria/ui/sound.rb +3 -3
  39. data/lib/alexandria/ui/ui_manager.rb +5 -13
  40. data/lib/alexandria/version.rb +2 -2
  41. data/spec/alexandria/book_providers_spec.rb +0 -17
  42. data/spec/alexandria/book_spec.rb +23 -0
  43. data/spec/alexandria/library_spec.rb +26 -0
  44. data/spec/alexandria/smart_library_spec.rb +11 -1
  45. data/spec/alexandria/ui/main_app_spec.rb +0 -11
  46. data/spec/spec_helper.rb +0 -1
  47. data/tasks/dogtail.rake +4 -0
  48. data/tasks/spec.rake +3 -3
  49. metadata +58 -12
  50. data/TODO +0 -24
  51. data/spec/alexandria/ui/listview_spec.rb +0 -28
  52. data/tasks/rdoc.rake +0 -6
@@ -22,13 +22,8 @@
22
22
  # iPod Notes support added 20 January 2008 by Tim Malone
23
23
  # require 'cgi'
24
24
 
25
- begin # image_size is optional
26
- $IMAGE_SIZE_LOADED = true
27
- require 'image_size'
28
- rescue LoadError
29
- $IMAGE_SIZE_LOADED = false
30
- puts "Can't load image_size, hence exported libraries are not optimized" if $DEBUG
31
- end
25
+ require 'csv'
26
+ require 'image_size'
32
27
 
33
28
  module Alexandria
34
29
  class LibrarySortOrder
@@ -55,8 +50,7 @@ module Alexandria
55
50
  end
56
51
 
57
52
  class Unsorted < LibrarySortOrder
58
- def initialize
59
- end
53
+ def initialize; end
60
54
 
61
55
  def sort(library)
62
56
  library
@@ -180,8 +174,9 @@ module Alexandria
180
174
  FileUtils.mkdir(filename) unless File.exist?(filename)
181
175
  Dir.chdir(filename) do
182
176
  copy_covers('pixmaps')
183
- FileUtils.cp_r(theme.pixmaps_directory,
184
- 'pixmaps') if theme.has_pixmaps?
177
+ if theme.has_pixmaps?
178
+ FileUtils.cp_r(theme.pixmaps_directory, 'pixmaps')
179
+ end
185
180
  FileUtils.cp(theme.css_file, '.')
186
181
  File.open('index.html', 'w') do |io|
187
182
  io << to_xhtml(File.basename(theme.css_file))
@@ -228,10 +223,15 @@ module Alexandria
228
223
  end
229
224
 
230
225
  def export_as_csv_list(filename)
231
- File.open(filename, 'w') do |io|
232
- io.puts 'Title' + ';' + 'Authors' + ';' + 'Publisher' + ';' + 'Edition' + ';' + 'ISBN' + ';' + 'Year Published' + ';' + 'Rating' + "(0 to #{UI::MainApp::MAX_RATING_STARS})" + ';' + 'Notes' + ';' + 'Want?' + ';' + 'Read?' + ';' + 'Own?' + ';' + 'Tags'
226
+ CSV.open(filename, 'w', col_sep: ';') do |csv|
227
+ csv << ['Title', 'Authors', 'Publisher', 'Edition', 'ISBN', 'Year Published',
228
+ "Rating(#{Book::DEFAULT_RATING} to #{Book::MAX_RATING_STARS})", 'Notes',
229
+ 'Want?', 'Read?', 'Own?', 'Tags']
233
230
  each do |book|
234
- io.puts book.title + ';' + book.authors.join(', ') + ';' + (book.publisher || '') + ';' + (book.edition || '') + ';' + (book.isbn || '') + ';' + (book.publishing_year.to_s || '') + ';' + (book.rating.to_s || '0') + ';' + (book.notes || '') + ';' + (book.want ? '1' : '0') + ';' + (book.redd ? '1' : '0') + ';' + (book.own ? '1' : '0') + ';' + (book.tags ? book.tags.join(', ') : '')
231
+ csv << [book.title, book.authors.join(', '), book.publisher, book.edition, book.isbn,
232
+ book.publishing_year, book.rating, book.notes,
233
+ (book.want ? '1' : '0'), (book.redd ? '1' : '0'), (book.own ? '1' : '0'),
234
+ (book.tags ? book.tags.join(', ') : '')]
235
235
  end
236
236
  end
237
237
  end
@@ -352,15 +352,10 @@ module Alexandria
352
352
  entry.add_element('cover').text = final_cover(book)
353
353
  image = images.add_element('image')
354
354
  image.add_attribute('id', final_cover(book))
355
- if $IMAGE_SIZE_LOADED
356
- image_s = ImageSize.new(IO.read(cover(book)))
357
- image.add_attribute('height', image_s.get_height.to_s)
358
- image.add_attribute('width', image_s.get_width.to_s)
359
- image.add_attribute('format', image_s.get_type)
360
- else
361
- image.add_attribute('format',
362
- Library.jpeg?(cover(book)) ? 'JPEG' : 'GIF')
363
- end
355
+ image_s = ImageSize.new(IO.read(cover(book)))
356
+ image.add_attribute('height', image_s.get_height.to_s)
357
+ image.add_attribute('width', image_s.get_width.to_s)
358
+ image.add_attribute('format', image_s.get_type)
364
359
  end
365
360
  end
366
361
  doc
@@ -409,12 +404,10 @@ EOS
409
404
  src="#{File.join('pixmaps', final_cover(book))}"
410
405
  alt="Cover file for '#{xhtml_escape(book.title)}'"
411
406
  EOS
412
- if $IMAGE_SIZE_LOADED
413
- image_s = ImageSize.new(IO.read(cover(book)))
414
- xhtml << <<EOS
407
+ image_s = ImageSize.new(IO.read(cover(book)))
408
+ xhtml << <<EOS
415
409
  height="#{image_s.get_height}" width="#{image_s.get_width}"
416
410
  EOS
417
- end
418
411
  xhtml << <<EOS
419
412
  />
420
413
  EOS
@@ -141,7 +141,7 @@ module Alexandria
141
141
  end
142
142
  puts cover
143
143
  book = Book.new(*book_elements)
144
- if elements['rating'] && (0..UI::MainApp::MAX_RATING_STARS).map.member?(elements['rating'].text.to_i)
144
+ if elements['rating'] && Book::VALID_RATINGS.member?(elements['rating'].text.to_i)
145
145
  book.rating = elements['rating'].text.to_i
146
146
  end
147
147
  book.notes = neaten(elements['comments'].text) if elements['comments']
@@ -278,8 +278,9 @@ module Alexandria
278
278
  # (on_error_cb and on_error_cb.call(e.message))
279
279
  end
280
280
 
281
- on_iterate_cb.call(current_iteration += 1,
282
- max_iterations) if on_iterate_cb
281
+ if on_iterate_cb
282
+ on_iterate_cb.call(current_iteration += 1, max_iterations)
283
+ end
283
284
  end
284
285
  puts "Bad Isbn list: #{bad_isbns.inspect}" if bad_isbns
285
286
  library = load(name)
@@ -290,8 +291,9 @@ module Alexandria
290
291
  puts "Saving #{book.isbn}..." if $DEBUG
291
292
  library << book
292
293
  library.save(book)
293
- on_iterate_cb.call(current_iteration += 1,
294
- max_iterations) if on_iterate_cb
294
+ if on_iterate_cb
295
+ on_iterate_cb.call(current_iteration += 1, max_iterations)
296
+ end
295
297
  end
296
298
  [library, bad_isbns, failed_lookup_isbns]
297
299
  end
@@ -18,10 +18,14 @@
18
18
  module Alexandria
19
19
  class Book
20
20
  attr_accessor :title, :authors, :isbn, :publisher, :publishing_year,
21
- :edition, :rating, :notes, :loaned, :loaned_since,
21
+ :edition, :notes, :loaned, :loaned_since,
22
22
  :loaned_to, :saved_ident, :redd, :redd_when, :own, :want, :tags, :version, :library
23
23
 
24
+ attr_reader :rating
25
+
24
26
  DEFAULT_RATING = 0
27
+ MAX_RATING_STARS = 5
28
+ VALID_RATINGS = (DEFAULT_RATING..MAX_RATING_STARS)
25
29
 
26
30
  def initialize(title, authors, isbn, publisher, publishing_year,
27
31
  edition)
@@ -38,6 +42,7 @@ module Alexandria
38
42
  @own = true
39
43
  @want = true
40
44
  @tags = []
45
+ @rating = DEFAULT_RATING
41
46
  # Need to implement bulk save function to update this
42
47
  @version = Alexandria::DATA_VERSION
43
48
  end
@@ -47,6 +52,11 @@ module Alexandria
47
52
  @isbn || @title.hash.to_s
48
53
  end
49
54
 
55
+ def rating=(rating)
56
+ raise ArgumentError unless VALID_RATINGS.include? rating
57
+ @rating = rating
58
+ end
59
+
50
60
  def loaned?
51
61
  loaned || false
52
62
  end
@@ -25,12 +25,6 @@ require 'open-uri'
25
25
  require 'observer'
26
26
  require 'singleton'
27
27
 
28
- class Array
29
- def sum
30
- reduce(0) { |a, b| a + b }
31
- end
32
- end
33
-
34
28
  module Alexandria
35
29
  class Library < Array
36
30
  include Logging
@@ -270,9 +264,6 @@ module Alexandria
270
264
  end
271
265
 
272
266
  class NoISBNError < StandardError
273
- def initialize(msg)
274
- super(msg)
275
- end
276
267
  end
277
268
 
278
269
  class InvalidISBNError < StandardError
@@ -308,8 +299,8 @@ module Alexandria
308
299
  end
309
300
 
310
301
  def self.ean_checksum(numbers)
311
- (10 - ([1, 3, 5, 7, 9, 11].map { |x| numbers[x] }.sum * 3 +
312
- [0, 2, 4, 6, 8, 10].map { |x| numbers[x] }.sum)) % 10
302
+ -(numbers.values_at(1, 3, 5, 7, 9, 11).reduce(:+) * 3 +
303
+ numbers.values_at(0, 2, 4, 6, 8, 10).reduce(:+)) % 10
313
304
  end
314
305
 
315
306
  def self.valid_ean?(ean)
@@ -323,8 +314,8 @@ module Alexandria
323
314
  end
324
315
 
325
316
  def self.upc_checksum(numbers)
326
- (10 - ([0, 2, 4, 6, 8, 10].map { |x| numbers[x] }.sum * 3 +
327
- [1, 3, 5, 7, 9].map { |x| numbers[x] }.sum)) % 10
317
+ -(numbers.values_at(0, 2, 4, 6, 8, 10).reduce(:+) * 3 +
318
+ numbers.values_at(1, 3, 5, 7, 9).reduce(:+)) % 10
328
319
  end
329
320
 
330
321
  def self.valid_upc?(upc)
@@ -571,8 +562,6 @@ module Alexandria
571
562
  something
572
563
  when Integer
573
564
  something
574
- when Integer
575
- something
576
565
  else
577
566
  raise "#{something} is a #{something.class}"
578
567
  end
@@ -587,8 +576,6 @@ module Alexandria
587
576
  something
588
577
  when Integer
589
578
  something
590
- when Integer
591
- something
592
579
  else
593
580
  raise "#{something} is #{something.class}"
594
581
  end
@@ -257,13 +257,13 @@ module Alexandria
257
257
  # range of values used by Alexandria.
258
258
  def discriminate(value)
259
259
  if value == 'true' # bool
260
- return true
260
+ true
261
261
  elsif value == 'false' # bool
262
- return false
262
+ false
263
263
  elsif value =~ /^[0-9]+$/ # int
264
- return value.to_i
264
+ value.to_i
265
265
  elsif value =~ /^\[(.*)\]$/ # list (assume of type String)
266
- return Regexp.last_match[1].split(',')
266
+ Regexp.last_match[1].split(',')
267
267
  elsif value =~ /^\((.*)\)$/ # pair (assume of type int)
268
268
  begin
269
269
  pair = Regexp.last_match[1].split(',')
@@ -272,7 +272,7 @@ module Alexandria
272
272
  return [0, 0]
273
273
  end
274
274
  else
275
- return value # string
275
+ value # string
276
276
  end
277
277
  end
278
278
  end
@@ -37,10 +37,9 @@ module Alexandria
37
37
  def initialize(name, rules, predicate_operator_rule)
38
38
  super()
39
39
  raise if name.nil? || rules.nil? || predicate_operator_rule.nil?
40
- @name = name
40
+ @name = name.dup.force_encoding('UTF-8')
41
41
  @rules = rules
42
42
  @predicate_operator_rule = predicate_operator_rule
43
- save
44
43
  libraries = Libraries.instance
45
44
  libraries.add_observer(self)
46
45
  self.libraries = libraries.all_regular_libraries
@@ -90,7 +89,7 @@ module Alexandria
90
89
  # Favorite books.
91
90
  rule = Rule.new(operands.find { |x| x.book_selector == :rating },
92
91
  Rule::Operators::IS,
93
- UI::MainApp::MAX_RATING_STARS.to_s)
92
+ Book::MAX_RATING_STARS.to_s)
94
93
  a << new(_('Favorite'), [rule], ALL_RULES)
95
94
 
96
95
  # Loaned books.
@@ -159,11 +158,6 @@ module Alexandria
159
158
  end
160
159
 
161
160
  def refilter
162
- raise 'need libraries' if @libraries.nil?
163
- raise 'no libraries' if @libraries.empty?
164
- raise 'need predicate operator' if @predicate_operator_rule.nil?
165
- raise 'need rule' if @rules.nil? || @rules.empty?
166
-
167
161
  filters = @rules.map(&:filter_proc)
168
162
  selector = @predicate_operator_rule == ALL_RULES ? :all? : :any?
169
163
 
@@ -199,7 +199,7 @@ module Alexandria
199
199
  end
200
200
 
201
201
  def on_set_rating
202
- (0..MAX_RATING_STARS).map do |rating|
202
+ Book::VALID_RATINGS.map do |rating|
203
203
  proc do
204
204
  books = selected_books
205
205
  library = selected_library
@@ -0,0 +1,9 @@
1
+ module Alexandria
2
+ module UI
3
+ module Columns
4
+ COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
5
+ ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
6
+ NOTES, REDD, OWN, WANT, TAGS, LOANED_TO = (0..17).to_a
7
+ end
8
+ end
9
+ end
@@ -44,6 +44,7 @@ module Alexandria
44
44
  min = 2
45
45
  completion.signal_connect('match-selected') do |c, model, iter|
46
46
  cur_text = c.entry.text
47
+ # TODO: Replace with iter[0] if possible
47
48
  new_tag = model.get_value(iter, 0)
48
49
  cur_text_split = cur_text.split(',')
49
50
  cur_text_split.delete_at(-1)
@@ -83,34 +84,6 @@ end
83
84
 
84
85
  Gtk::Entry.prepend Alexandria::EntryOverrides
85
86
 
86
- begin
87
- require 'revolution'
88
-
89
- EVOLUTION_CONTACTS =
90
- Revolution::Revolution.new.get_all_contacts.map do |contact|
91
- first = contact.first_name
92
- last = contact.last_name
93
-
94
- if first
95
- first.strip!
96
- first = nil if first.empty?
97
- end
98
-
99
- if last
100
- last.strip!
101
- last = nil if last.empty?
102
- end
103
-
104
- first && last ? first + ' ' + last : first ? first : last
105
- end
106
- rescue LoadError => e
107
- Alexandria.log.debug { 'Could not find optional ruby-revolution; Evolution contacts will not be loaded' }
108
- EVOLUTION_CONTACTS = [].freeze
109
- rescue => e
110
- Alexandria.log.warn { e.message }
111
- EVOLUTION_CONTACTS = [].freeze
112
- end
113
-
114
87
  module Alexandria
115
88
  module UI
116
89
  class CompletionModels
@@ -207,7 +180,6 @@ module Alexandria
207
180
  end
208
181
  end
209
182
 
210
- borrowers.concat(EVOLUTION_CONTACTS)
211
183
  borrowers.uniq!
212
184
 
213
185
  tags.uniq!
@@ -236,8 +236,7 @@ module Alexandria
236
236
  @acquire_dialog.destroy
237
237
  end
238
238
 
239
- def on_help
240
- end
239
+ def on_help; end
241
240
 
242
241
  def read_barcode_scan
243
242
  @animation.start
@@ -535,7 +534,7 @@ module Alexandria
535
534
  def play_sound(effect)
536
535
  if effect == 'scanning'
537
536
  puts "Effect: #{effect}, playing: #{@prefs.play_scanning_sound}" if $DEBUG
538
- return unless @prefs.play_scanning_sound
537
+ return unless @prefs.play_scanning_sound
539
538
  @sound_players['scanning'].play('scanning')
540
539
  else
541
540
  puts "Effect: #{effect}, playing: #{@prefs.play_scan_sound}" if $DEBUG
@@ -268,8 +268,7 @@ module Alexandria
268
268
  end
269
269
  end
270
270
 
271
- def want_toggled
272
- end
271
+ def want_toggled; end
273
272
 
274
273
  @@latest_filechooser_directory = ENV['HOME']
275
274
  def on_change_cover
@@ -84,8 +84,9 @@ module Alexandria
84
84
  ary = @library.select { |book|
85
85
  book.ident == @entry_isbn.text
86
86
  }
87
- raise AddError, _('The EAN/ISBN you provided is ' \
88
- 'already used in this library.') unless ary.empty?
87
+ unless ary.empty?
88
+ raise AddError, _('The EAN/ISBN you provided is already used in this library.')
89
+ end
89
90
  isbn = begin
90
91
  Library.canonicalise_isbn(@entry_isbn.text)
91
92
  rescue Alexandria::Library::InvalidISBNError
@@ -17,16 +17,11 @@
17
17
  # write to the Free Software Foundation, Inc., 51 Franklin Street,
18
18
  # Fifth Floor, Boston, MA 02110-1301 USA.
19
19
 
20
+ require 'alexandria/ui/columns'
20
21
  require 'alexandria/ui/iconview_tooltips'
21
22
 
22
23
  module Alexandria
23
24
  module UI
24
- module Columns
25
- COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
26
- ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
27
- NOTES, REDD, OWN, WANT, TAGS = (0..16).to_a
28
- end
29
-
30
25
  class IconViewManager
31
26
  ICON_WIDTH = 60
32
27
  include Logging
@@ -32,6 +32,7 @@ module Alexandria
32
32
  end
33
33
  clear
34
34
  set_row_separator_func do |model, iter|
35
+ # TODO: Replace with iter[1] if possible
35
36
  model.get_value(iter, 1) == '-'
36
37
  end
37
38
  self.model = Gtk::ListStore.new(GdkPixbuf::Pixbuf, String, TrueClass)
@@ -18,6 +18,8 @@
18
18
  # write to the Free Software Foundation, Inc., 51 Franklin Street,
19
19
  # Fifth Floor, Boston, MA 02110-1301 USA.
20
20
 
21
+ require 'alexandria/ui/columns'
22
+
21
23
  module Alexandria
22
24
  module UI
23
25
  include Logging
@@ -29,13 +31,6 @@ module Alexandria
29
31
  include DragAndDropable
30
32
  BOOKS_TARGET_TABLE = [['ALEXANDRIA_BOOKS', :same_app, 0]].freeze
31
33
 
32
- MAX_RATING_STARS = 5
33
- module Columns
34
- COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
35
- ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
36
- NOTES, REDD, OWN, WANT, TAGS, LOANED_TO = (0..17).to_a
37
- end
38
-
39
34
  def initialize(_listview, parent)
40
35
  @parent = parent
41
36
  @prefs = @parent.prefs
@@ -127,14 +122,14 @@ module Alexandria
127
122
  log.debug { 'Create listview column for %s...' % title }
128
123
  column = Gtk::TreeViewColumn.new(title)
129
124
  column.sizing = :fixed
130
- width = (Icons::STAR_SET.width + 1) * MAX_RATING_STARS
125
+ width = (Icons::STAR_SET.width + 1) * Book::MAX_RATING_STARS
131
126
  column.fixed_width = column.min_width = column.max_width = width
132
- MAX_RATING_STARS.times do |i|
127
+ Book::MAX_RATING_STARS.times do |i|
133
128
  renderer = Gtk::CellRendererPixbuf.new
134
129
  renderer.xalign = 0.0
135
130
  column.pack_start(renderer, false)
136
131
  column.set_cell_data_func(renderer) do |_tree_column, cell, _tree_model, iter|
137
- rating = (iter[Columns::RATING] - MAX_RATING_STARS).abs
132
+ rating = (iter[Columns::RATING] - Book::MAX_RATING_STARS).abs
138
133
  cell.pixbuf = rating >= i.succ ?
139
134
  Icons::STAR_SET : Icons::STAR_UNSET
140
135
  end