alexandria-book-collection-manager 0.7.5 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +9 -0
  3. data/.github/workflows/ruby.yml +72 -0
  4. data/.gitignore +4 -1
  5. data/.rubocop.yml +65 -30
  6. data/.rubocop_todo.yml +49 -165
  7. data/.simplecov +5 -2
  8. data/CHANGELOG.md +64 -0
  9. data/ChangeLog.0 +19 -19
  10. data/INSTALL.md +26 -16
  11. data/README.md +31 -35
  12. data/Rakefile +18 -16
  13. data/alexandria-book-collection-manager.gemspec +35 -29
  14. data/doc/FAQ +2 -2
  15. data/doc/dependency_decisions.yml +22 -3
  16. data/lib/alexandria/about.rb +1 -1
  17. data/lib/alexandria/book_providers/bl_provider.rb +88 -0
  18. data/lib/alexandria/book_providers/douban.rb +2 -2
  19. data/lib/alexandria/book_providers/loc_provider.rb +38 -0
  20. data/lib/alexandria/book_providers/pseudomarc.rb +61 -71
  21. data/lib/alexandria/book_providers/sbn_provider.rb +108 -0
  22. data/lib/alexandria/book_providers/{thalia.rb → thalia_provider.rb} +37 -74
  23. data/lib/alexandria/book_providers/web.rb +2 -2
  24. data/lib/alexandria/book_providers/worldcat.rb +34 -38
  25. data/lib/alexandria/book_providers/z3950_provider.rb +199 -0
  26. data/lib/alexandria/book_providers.rb +48 -65
  27. data/lib/alexandria/default_preferences.rb +2 -1
  28. data/lib/alexandria/execution_queue.rb +13 -12
  29. data/lib/alexandria/export_library.rb +21 -22
  30. data/lib/alexandria/image_fetcher.rb +25 -0
  31. data/lib/alexandria/import_library.rb +46 -70
  32. data/lib/alexandria/import_library_csv.rb +16 -16
  33. data/lib/alexandria/library_sort_order.rb +3 -1
  34. data/lib/alexandria/library_store.rb +19 -20
  35. data/lib/alexandria/logging.rb +5 -9
  36. data/lib/alexandria/models/book.rb +15 -2
  37. data/lib/alexandria/models/library.rb +31 -35
  38. data/lib/alexandria/net.rb +1 -2
  39. data/lib/alexandria/preferences.rb +27 -33
  40. data/lib/alexandria/scanners/cue_cat.rb +6 -6
  41. data/lib/alexandria/scanners/keyboard.rb +1 -1
  42. data/lib/alexandria/scanners.rb +2 -2
  43. data/lib/alexandria/smart_library.rb +22 -26
  44. data/lib/alexandria/ui/about_dialog.rb +1 -1
  45. data/lib/alexandria/ui/acquire_dialog.rb +15 -19
  46. data/lib/alexandria/ui/alert_dialog.rb +36 -19
  47. data/lib/alexandria/ui/bad_isbns_dialog.rb +13 -9
  48. data/lib/alexandria/ui/barcode_animation.rb +6 -6
  49. data/lib/alexandria/ui/book_properties_dialog.rb +2 -3
  50. data/lib/alexandria/ui/book_properties_dialog_base.rb +35 -137
  51. data/lib/alexandria/ui/calendar_popup.rb +58 -0
  52. data/lib/alexandria/ui/callbacks.rb +144 -123
  53. data/lib/alexandria/ui/completion_models.rb +2 -6
  54. data/lib/alexandria/ui/confirm_erase_dialog.rb +1 -1
  55. data/lib/alexandria/ui/conflict_while_copying_dialog.rb +2 -2
  56. data/lib/alexandria/ui/error_dialog.rb +1 -1
  57. data/lib/alexandria/ui/export_dialog.rb +19 -18
  58. data/lib/alexandria/ui/icons.rb +34 -40
  59. data/lib/alexandria/ui/iconview_tooltips.rb +40 -53
  60. data/lib/alexandria/ui/import_dialog.rb +49 -48
  61. data/lib/alexandria/ui/init.rb +14 -12
  62. data/lib/alexandria/ui/keep_bad_isbn_dialog.rb +2 -2
  63. data/lib/alexandria/ui/libraries_combo.rb +10 -9
  64. data/lib/alexandria/ui/listview.rb +6 -7
  65. data/lib/alexandria/ui/main_app.rb +2 -2
  66. data/lib/alexandria/ui/multi_drag_treeview.rb +5 -7
  67. data/lib/alexandria/ui/new_book_dialog.rb +63 -65
  68. data/lib/alexandria/ui/new_book_dialog_manual.rb +1 -1
  69. data/lib/alexandria/ui/new_provider_dialog.rb +12 -11
  70. data/lib/alexandria/ui/new_smart_library_dialog.rb +39 -27
  71. data/lib/alexandria/ui/preferences_dialog.rb +25 -84
  72. data/lib/alexandria/ui/provider_preferences_base_dialog.rb +10 -6
  73. data/lib/alexandria/ui/provider_preferences_dialog.rb +5 -5
  74. data/lib/alexandria/ui/really_delete_dialog.rb +2 -2
  75. data/lib/alexandria/ui/sidepane_manager.rb +38 -38
  76. data/lib/alexandria/ui/skip_entry_dialog.rb +3 -2
  77. data/lib/alexandria/ui/smart_library_properties_dialog.rb +35 -36
  78. data/lib/alexandria/ui/smart_library_properties_dialog_base.rb +61 -244
  79. data/lib/alexandria/ui/smart_library_rule_box.rb +119 -0
  80. data/lib/alexandria/ui/sound.rb +4 -6
  81. data/lib/alexandria/ui/ui_manager.rb +80 -83
  82. data/lib/alexandria/ui.rb +7 -7
  83. data/lib/alexandria/version.rb +2 -2
  84. data/lib/alexandria/web_themes.rb +15 -15
  85. data/lib/alexandria.rb +2 -2
  86. data/po/cs.po +947 -865
  87. data/po/cy.po +913 -864
  88. data/po/de.po +961 -865
  89. data/po/el.po +956 -861
  90. data/po/es.po +952 -857
  91. data/po/fr.po +950 -865
  92. data/po/ga.po +866 -819
  93. data/po/gl.po +946 -861
  94. data/po/it.po +945 -858
  95. data/po/ja.po +921 -836
  96. data/po/mk.po +953 -858
  97. data/po/nb.po +932 -847
  98. data/po/nl.po +955 -849
  99. data/po/pl.po +999 -963
  100. data/po/pt.po +946 -850
  101. data/po/pt_BR.po +944 -859
  102. data/po/ru.po +959 -868
  103. data/po/sk.po +950 -863
  104. data/po/sv.po +944 -859
  105. data/po/uk.po +925 -846
  106. data/po/zh_TW.po +926 -841
  107. data/schemas/alexandria.schemas +1 -1
  108. data/share/alexandria/glade/main_app__builder.glade +6 -21
  109. data/share/gnome/help/alexandria/C/adding-books.xml +3 -4
  110. data/share/gnome/help/alexandria/C/introduction.xml +0 -16
  111. data/share/gnome/help/alexandria/C/searching.xml +1 -4
  112. data/share/gnome/help/alexandria/C/settings.xml +0 -30
  113. data/share/gnome/help/alexandria/C/smart-libraries.xml +2 -2
  114. data/share/gnome/help/alexandria/C/working-with-libraries.xml +1 -1
  115. data/share/gnome/help/alexandria/fr/alexandria.xml +5 -160
  116. data/share/gnome/help/alexandria/ja/adding-books.xml +1 -1
  117. data/share/gnome/help/alexandria/ja/introduction.xml +0 -15
  118. data/share/gnome/help/alexandria/ja/searching.xml +3 -7
  119. data/share/gnome/help/alexandria/ja/settings.xml +0 -27
  120. data/share/gnome/help/alexandria/ja/smart-libraries.xml +1 -1
  121. data/spec/alexandria/book_providers/bl_provider_spec.rb +13 -0
  122. data/spec/alexandria/book_providers/loc_provider_spec.rb +17 -0
  123. data/spec/alexandria/book_providers/sbn_provider_spec.rb +13 -0
  124. data/spec/alexandria/book_providers/thalia_provider_spec.rb +119 -0
  125. data/spec/alexandria/book_providers/world_cat_provider_spec.rb +160 -0
  126. data/spec/alexandria/book_providers_spec.rb +0 -154
  127. data/spec/alexandria/console_spec.rb +0 -5
  128. data/spec/alexandria/export_library_spec.rb +27 -38
  129. data/spec/alexandria/library_spec.rb +76 -46
  130. data/spec/alexandria/preferences_spec.rb +29 -3
  131. data/spec/alexandria/scanners/cue_cat_spec.rb +1 -1
  132. data/spec/alexandria/ui/about_dialog_spec.rb +1 -1
  133. data/spec/alexandria/ui/acquire_dialog_spec.rb +1 -1
  134. data/spec/alexandria/ui/alert_dialog_spec.rb +1 -1
  135. data/spec/alexandria/ui/bad_isbns_dialog_spec.rb +1 -1
  136. data/spec/alexandria/ui/book_properties_dialog_spec.rb +47 -5
  137. data/spec/alexandria/ui/confirm_erase_dialog_spec.rb +1 -1
  138. data/spec/alexandria/ui/conflict_while_copying_dialog_spec.rb +1 -1
  139. data/spec/alexandria/ui/error_dialog_spec.rb +1 -1
  140. data/spec/alexandria/ui/export_dialog_spec.rb +25 -4
  141. data/spec/alexandria/ui/icons_spec.rb +26 -0
  142. data/spec/alexandria/ui/iconview_spec.rb +1 -1
  143. data/spec/alexandria/ui/import_dialog_spec.rb +35 -3
  144. data/spec/alexandria/ui/keep_bad_isbn_dialog_spec.rb +1 -1
  145. data/spec/alexandria/ui/main_app_spec.rb +1 -1
  146. data/spec/alexandria/ui/new_book_dialog_manual_spec.rb +39 -3
  147. data/spec/alexandria/ui/new_provider_dialog_spec.rb +19 -3
  148. data/spec/alexandria/ui/new_smart_library_dialog_spec.rb +28 -3
  149. data/spec/alexandria/ui/preferences_dialog_spec.rb +2 -2
  150. data/spec/alexandria/ui/provider_preferences_dialog_spec.rb +23 -8
  151. data/spec/alexandria/ui/really_delete_dialog_spec.rb +1 -1
  152. data/spec/alexandria/ui/sidepane_manager_spec.rb +2 -2
  153. data/spec/alexandria/ui/skip_entry_dialog_spec.rb +1 -1
  154. data/spec/alexandria/ui/smart_library_properties_dialog_spec.rb +37 -6
  155. data/spec/alexandria/ui/ui_manager_spec.rb +116 -2
  156. data/spec/data/libraries/0.6.2/My Library/9780571147168.yaml +2 -0
  157. data/spec/end_to_end/basic_run_spec.rb +3 -8
  158. data/spec/fixtures/cover.jpg +0 -0
  159. data/spec/spec_helper.rb +47 -3
  160. data/tasks/spec.rake +3 -5
  161. data/util/rake/fileinstall.rb +16 -15
  162. data/util/rake/omfgenerate.rb +1 -1
  163. metadata +141 -52
  164. data/.travis.yml +0 -39
  165. data/lib/alexandria/book_providers/adlibris.rb +0 -196
  166. data/lib/alexandria/book_providers/amazon_aws.rb +0 -252
  167. data/lib/alexandria/book_providers/amazon_ecs_util.rb +0 -388
  168. data/lib/alexandria/book_providers/barnes_and_noble.rb +0 -209
  169. data/lib/alexandria/book_providers/proxis.rb +0 -175
  170. data/lib/alexandria/book_providers/siciliano.rb +0 -257
  171. data/lib/alexandria/book_providers/z3950.rb +0 -415
  172. data/spec/alexandria/ui/ui_utilities_spec.rb +0 -62
  173. data/spec/alexandria/utilities_spec.rb +0 -52
@@ -10,13 +10,16 @@ require "rexml/document"
10
10
  require "tempfile"
11
11
  require "etc"
12
12
  require "alexandria/library_store"
13
+ require "alexandria/image_fetcher"
13
14
 
14
15
  module Alexandria
15
16
  class Library < Array
16
17
  include Logging
18
+ include ImageFetcher
17
19
 
18
20
  attr_reader :name
19
21
  attr_accessor :ruined_books, :updating, :deleted_books
22
+
20
23
  DEFAULT_DIR = File.join(ENV["HOME"], ".alexandria")
21
24
  EXT = { book: ".yaml", cover: ".cover" }.freeze
22
25
 
@@ -76,7 +79,7 @@ module Alexandria
76
79
  normalized = entry.delete("- ").upcase
77
80
  return [] unless /\A[\dX]*\Z/.match?(normalized)
78
81
 
79
- normalized.split("").map do |char|
82
+ normalized.chars.map do |char|
80
83
  char == "X" ? 10 : char.to_i
81
84
  end
82
85
  end
@@ -95,8 +98,8 @@ module Alexandria
95
98
  end
96
99
 
97
100
  def self.ean_checksum(numbers)
98
- -(numbers.values_at(1, 3, 5, 7, 9, 11).reduce(:+) * 3 +
99
- numbers.values_at(0, 2, 4, 6, 8, 10).reduce(:+)) % 10
101
+ -(numbers.values_at(1, 3, 5, 7, 9, 11).sum * 3 +
102
+ numbers.values_at(0, 2, 4, 6, 8, 10).sum) % 10
100
103
  end
101
104
 
102
105
  def self.valid_ean?(ean)
@@ -108,8 +111,8 @@ module Alexandria
108
111
  end
109
112
 
110
113
  def self.upc_checksum(numbers)
111
- -(numbers.values_at(0, 2, 4, 6, 8, 10).reduce(:+) * 3 +
112
- numbers.values_at(1, 3, 5, 7, 9).reduce(:+)) % 10
114
+ -(numbers.values_at(0, 2, 4, 6, 8, 10).sum * 3 +
115
+ numbers.values_at(1, 3, 5, 7, 9).sum) % 10
113
116
  end
114
117
 
115
118
  def self.valid_upc?(upc)
@@ -221,11 +224,11 @@ module Alexandria
221
224
  File.open(yaml(temp_book), "w") { |io| io.puts temp_book.to_yaml }
222
225
 
223
226
  # Do not notify twice.
224
- if changed?
225
- notify_observers(self,
226
- already_there ? BOOK_UPDATED : BOOK_ADDED,
227
- book)
228
- end
227
+ return unless changed?
228
+
229
+ notify_observers(self,
230
+ already_there ? BOOK_UPDATED : BOOK_ADDED,
231
+ book)
229
232
  end
230
233
 
231
234
  def transport
@@ -237,19 +240,13 @@ module Alexandria
237
240
  Dir.chdir(path) do
238
241
  # Fetch the cover picture.
239
242
  cover_file = cover(book)
240
- File.open(cover_file, "w") do |io|
241
- uri = URI.parse(cover_uri)
242
- if uri.scheme.nil?
243
- # Regular filename.
244
- File.open(cover_uri) { |io2| io.puts io2.read }
245
- else
246
- # Try open-uri.
247
- io.puts transport.get(uri)
248
- end
249
- end
243
+ data = fetch_image(cover_uri)
244
+ if data
245
+ File.write(cover_file, data)
250
246
 
251
- # Remove the file if its blank.
252
- File.delete(cover_file) if Alexandria::UI::Icons.blank?(cover_file)
247
+ # Remove the file if its blank.
248
+ File.delete(cover_file) if Alexandria::UI::Icons.blank?(cover_file)
249
+ end
253
250
  end
254
251
  end
255
252
 
@@ -283,7 +280,10 @@ module Alexandria
283
280
  else
284
281
  if @deleted_books.include?(book)
285
282
  doubles = @deleted_books.select { |b| b.equal?(book) }
286
- raise ArgumentError, "Book #{book.isbn} was already deleted" unless doubles.empty?
283
+ unless doubles.empty?
284
+ raise ArgumentError, format(_("Book %<isbn>s was already deleted"),
285
+ isbn: book.isbn)
286
+ end
287
287
  end
288
288
  @deleted_books << book
289
289
  i = index(book)
@@ -341,12 +341,10 @@ module Alexandria
341
341
  else
342
342
  "g#{something.ident}" # g is for generated id...
343
343
  end
344
- when String
345
- something
346
- when Integer
344
+ when String, Integer
347
345
  something
348
346
  else
349
- raise "#{something} is a #{something.class}"
347
+ raise NotImplementedError
350
348
  end
351
349
  File.join(path, ident.to_s + EXT[:cover])
352
350
  end
@@ -355,18 +353,16 @@ module Alexandria
355
353
  ident = case something
356
354
  when Book
357
355
  something.ident
358
- when String
359
- something
360
- when Integer
356
+ when String, Integer
361
357
  something
362
358
  else
363
- raise "#{something} is #{something.class}"
359
+ raise NotImplementedError
364
360
  end
365
361
  File.join(basedir, ident.to_s + EXT[:book])
366
362
  end
367
363
 
368
364
  def name=(name)
369
- File.rename(path, File.join(dir, name))
365
+ File.rename(path, File.join(@store.library_dir, name))
370
366
  @name = name
371
367
  end
372
368
 
@@ -378,8 +374,8 @@ module Alexandria
378
374
  length - n_rated
379
375
  end
380
376
 
381
- def ==(object)
382
- object.is_a?(self.class) && object.name == name
377
+ def ==(other)
378
+ other.is_a?(self.class) && other.name == name
383
379
  end
384
380
 
385
381
  def copy_covers(somewhere)
@@ -394,7 +390,7 @@ module Alexandria
394
390
  end
395
391
 
396
392
  def self.jpeg?(file)
397
- IO.read(file, 10)[6..9] == "JFIF"
393
+ File.read(file, 10)[6..9] == "JFIF"
398
394
  end
399
395
 
400
396
  def final_cover(book)
@@ -38,10 +38,9 @@ module Alexandria
38
38
  @extra_request_headers.each_pair do |header_name, value|
39
39
  req.add_field(header_name, value)
40
40
  end
41
- res = WWWAgent.transport.start(uri.host, uri.port) do |http|
41
+ WWWAgent.transport.start(uri.host, uri.port) do |http|
42
42
  http.request(req)
43
43
  end
44
- res
45
44
  end
46
45
 
47
46
  def language=(lang)
@@ -57,30 +57,24 @@ module Alexandria
57
57
  @changed_settings.clear
58
58
  end
59
59
 
60
- def method_missing(id, *args)
61
- method = id.id2name
62
- if (match = /(.*)=$/.match(method))
63
- if args.length != 1
64
- raise "Set method #{method} should be called with " \
65
- "only one argument (was called with #{args.length})"
66
- end
67
- variable_name = match[1]
68
- new_value = args.first
69
- generic_setter(variable_name, new_value)
70
- else
71
- unless args.empty?
72
- raise "Get method #{method} should be called " \
73
- "without argument (was called with #{args.length})"
74
- end
75
- generic_getter(method)
76
- end
77
- end
78
-
79
60
  def remove_preference(variable_name)
80
61
  @alexandria_settings.delete(variable_name)
81
62
  @changed_settings << variable_name
82
63
  end
83
64
 
65
+ DEFAULT_VALUES.each_key do |var|
66
+ define_method(var) { generic_getter var }
67
+ define_method("#{var}=") { |val| generic_setter var, val }
68
+ end
69
+
70
+ def get_variable(variable_name)
71
+ generic_getter(variable_name.to_s)
72
+ end
73
+
74
+ def set_variable(variable_name, value)
75
+ generic_setter(variable_name.to_s, value)
76
+ end
77
+
84
78
  private
85
79
 
86
80
  ##
@@ -140,11 +134,10 @@ module Alexandria
140
134
  ##
141
135
 
142
136
  def get_gconf_type(value)
143
- if value.is_a?(String)
144
- "string"
145
- elsif value.is_a?(Integer)
137
+ case value
138
+ when Integer
146
139
  "int"
147
- elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
140
+ when TrueClass, FalseClass
148
141
  "bool"
149
142
  else
150
143
  "string"
@@ -152,7 +145,7 @@ module Alexandria
152
145
  end
153
146
 
154
147
  def exec_gconf_set_list(var_path, new_list)
155
- # NOTE we must check between list and pair...
148
+ # NOTE: we must check between list and pair...
156
149
 
157
150
  list_type = get_gconf_type(new_list.first)
158
151
  if list_type == "int" && new_list.size == 2
@@ -168,24 +161,24 @@ module Alexandria
168
161
  end
169
162
 
170
163
  def make_list_string(list)
171
- list.map! { |x| x.gsub(/\"/, '\\"') } if get_gconf_type(list.first) == "string"
164
+ list.map! { |x| x.gsub(/"/, '\\"') } if get_gconf_type(list.first) == "string"
172
165
  contents = list.join(",")
173
166
  "[" + contents + "]"
174
167
  end
175
168
 
176
169
  def exec_gconf_set(var_path, new_value)
177
170
  if /cols_width/.match?(var_path)
178
- puts new_value
171
+ log.debug { new_value }
179
172
 
180
173
  # new_value = {}
181
174
  end
182
175
  type = get_gconf_type(new_value)
183
176
  value_str = new_value
184
177
  if new_value.is_a? String
185
- new_value = new_value.gsub(/\"/, '\\"')
178
+ new_value = new_value.gsub(/"/, '\\"')
186
179
  value_str = "\"#{new_value}\""
187
180
  end
188
- puts value_str if /cols_width/.match?(var_path)
181
+ log.debug { value_str } if /cols_width/.match?(var_path)
189
182
  `gconftool-2 --type #{type} --set #{var_path} #{value_str}`
190
183
  end
191
184
 
@@ -242,15 +235,16 @@ module Alexandria
242
235
  # gconftool. This is not fool-proof, but it *does* work for the
243
236
  # range of values used by Alexandria.
244
237
  def discriminate(value)
245
- if value == "true" # bool
238
+ case value
239
+ when "true" # bool
246
240
  true
247
- elsif value == "false" # bool
241
+ when "false" # bool
248
242
  false
249
- elsif /^[0-9]+$/.match?(value) # int
243
+ when /^-?[0-9]+$/ # int
250
244
  value.to_i
251
- elsif value =~ /^\[(.*)\]$/ # list (assume of type String)
245
+ when /^\[(.*)\]$/ # list (assume of type String)
252
246
  Regexp.last_match[1].split(",")
253
- elsif value =~ /^\((.*)\)$/ # pair (assume of type int)
247
+ when /^\((.*)\)$/ # pair (assume of type int)
254
248
  begin
255
249
  pair = Regexp.last_match[1].split(",")
256
250
  [discriminate(pair.first), discriminate(pair.last)]
@@ -23,7 +23,7 @@ require "alexandria/scanners"
23
23
  module Alexandria
24
24
  module Scanners
25
25
  class CueCat
26
- include Alexandria::Logging
26
+ include Logging
27
27
 
28
28
  def name
29
29
  "CueCat"
@@ -72,7 +72,8 @@ module Alexandria
72
72
 
73
73
  return code if type == "IBN"
74
74
 
75
- raise "Don't know how to handle type #{type} (barcode: #{code})"
75
+ raise format(_("Don't know how to handle type %<type>s (barcode: %<code>s)"),
76
+ type: type, code: code)
76
77
  end
77
78
 
78
79
  private
@@ -80,13 +81,12 @@ module Alexandria
80
81
  def decode_field(encoded)
81
82
  seq = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-"
82
83
 
83
- chars = encoded.split(//)
84
+ chars = encoded.chars
84
85
  values = chars.map { |c| seq.index(c) }
85
86
 
86
87
  padding = pad(values)
87
88
  result = calc(values)
88
- result = result[0, result.length - padding]
89
- result
89
+ result[0, result.length - padding]
90
90
  end
91
91
 
92
92
  def calc(values)
@@ -106,7 +106,7 @@ module Alexandria
106
106
  length = array.length % 4
107
107
 
108
108
  if length.nonzero?
109
- raise "Error parsing CueCat input" if length == 1
109
+ raise _("Error parsing CueCat input") if length == 1
110
110
 
111
111
  length = 4 - length
112
112
  length.times { array.push(0) }
@@ -48,7 +48,7 @@ module Alexandria
48
48
  elsif data.length >= 13
49
49
  data[0, 13]
50
50
  else
51
- raise "Unknown scan data #{data}"
51
+ raise format(_("Unknown scan data %s<data>"), data: data)
52
52
  end
53
53
  end
54
54
  end
@@ -40,8 +40,8 @@ module Alexandria
40
40
  registry.find { |scanner| scanner.name == name }
41
41
  end
42
42
 
43
- def self.each_scanner
44
- registry.each { |scanner| yield scanner }
43
+ def self.each_scanner(&block)
44
+ registry.each(&block)
45
45
  end
46
46
 
47
47
  private_class_method :registry
@@ -16,7 +16,7 @@ module Alexandria
16
16
 
17
17
  ALL_RULES = 1
18
18
  ANY_RULE = 2
19
- attr_reader :name
19
+ attr_reader :name, :n_rated
20
20
  attr_accessor :rules, :predicate_operator_rule, :deleted_books
21
21
 
22
22
  EXT = ".yaml"
@@ -94,22 +94,23 @@ module Alexandria
94
94
  end
95
95
 
96
96
  def name=(new_name)
97
- if @name != new_name
98
- old_yaml = yaml
99
- @name = new_name
100
- FileUtils.mv(old_yaml, yaml)
101
- save
102
- end
97
+ return unless @name != new_name
98
+
99
+ old_yaml = yaml
100
+ @name = new_name
101
+ FileUtils.mv(old_yaml, yaml)
102
+ save
103
103
  end
104
104
 
105
105
  def update(*params)
106
- if params.first.is_a?(LibraryCollection)
106
+ case params.first
107
+ when LibraryCollection
107
108
  libraries, _, library = params
108
109
  unless library.is_a?(self.class)
109
110
  self.libraries = libraries.all_libraries
110
111
  refilter
111
112
  end
112
- elsif params.first.is_a?(Library)
113
+ when Library
113
114
  refilter
114
115
  end
115
116
  end
@@ -172,14 +173,12 @@ module Alexandria
172
173
  end
173
174
  end
174
175
 
175
- attr_reader :n_rated
176
-
177
176
  def n_unrated
178
177
  length - n_rated
179
178
  end
180
179
 
181
- def ==(object)
182
- object.is_a?(self.class) && object.name == name
180
+ def ==(other)
181
+ other.is_a?(self.class) && other.name == name
183
182
  end
184
183
 
185
184
  @@deleted_libraries = []
@@ -190,15 +189,16 @@ module Alexandria
190
189
 
191
190
  def self.really_delete_deleted_libraries
192
191
  @@deleted_libraries.each do |library|
193
- puts "Deleting smart library file (#{yaml})" if $DEBUG
192
+ log.debug { "Deleting smart library file (#{yaml})" }
194
193
  FileUtils.rm_rf(library.yaml)
195
194
  end
196
195
  end
197
196
 
198
197
  def delete
199
198
  if @@deleted_libraries.include?(self)
200
- puts "Already deleted a SmartLibrary with this name"
201
- puts "(this might mess up undeletes...)"
199
+ log.info do
200
+ "Already deleted a SmartLibrary with this name (this might mess up undeletes)"
201
+ end
202
202
  FileUtils.rm_rf(yaml)
203
203
  # so we just delete the old smart library, and
204
204
  # 'pending' delete the new one of the same name...
@@ -265,8 +265,8 @@ module Alexandria
265
265
 
266
266
  Operand = Struct.new(:name, :klass)
267
267
  class Operand
268
- def <=>(x)
269
- name <=> x.name
268
+ def <=>(other)
269
+ name <=> other.name
270
270
  end
271
271
  end
272
272
 
@@ -281,8 +281,8 @@ module Alexandria
281
281
 
282
282
  Operator = Struct.new(:sym, :name, :proc)
283
283
  class Operator
284
- def <=>(x)
285
- name <=> x.name
284
+ def <=>(other)
285
+ name <=> other.name
286
286
  end
287
287
  end
288
288
 
@@ -462,17 +462,13 @@ module Alexandria
462
462
  end
463
463
  end
464
464
  else
465
- raise "invalid operand klass #{operand.klass}"
465
+ raise format(_("invalid operand klass %<klass>s"), klass: operand.klass)
466
466
  end
467
467
  end
468
468
 
469
469
  def filter_proc
470
470
  proc do |book|
471
- begin
472
- left_value = book.send(@operand.book_selector)
473
- rescue StandardError => ex
474
- puts ex.message
475
- end
471
+ left_value = book.send(@operand.book_selector)
476
472
  right_value = @value
477
473
  if right_value.is_a?(String)
478
474
  left_value = left_value.to_s.downcase
@@ -7,7 +7,7 @@
7
7
  module Alexandria
8
8
  module UI
9
9
  class AboutDialog
10
- GPL = <<~EOL
10
+ GPL = <<~EOL # rubocop:disable I18n/GetText/DecorateString
11
11
  Alexandria is free software; you can redistribute it and/or
12
12
  modify it under the terms of the GNU General Public License as
13
13
  published by the Free Software Foundation; either version 2 of the
@@ -5,6 +5,9 @@
5
5
  # See the file README.md for authorship and licensing information.
6
6
 
7
7
  require "monitor"
8
+
9
+ require "alexandria/image_fetcher"
10
+
8
11
  require "alexandria/scanners/cue_cat"
9
12
  require "alexandria/scanners/keyboard"
10
13
 
@@ -40,6 +43,7 @@ module Alexandria
40
43
 
41
44
  class AcquireDialog < BuilderBase
42
45
  include GetText
46
+ include ImageFetcher
43
47
  include Logging
44
48
  extend GetText
45
49
  GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
@@ -64,6 +68,10 @@ module Alexandria
64
68
  @search_threads_running = @search_thread_counter.new_cond
65
69
  end
66
70
 
71
+ def show
72
+ @acquire_dialog.show
73
+ end
74
+
67
75
  def widget_names
68
76
  [:acquire_dialog, :add_button, :barcodes_treeview, :barcode_label,
69
77
  :scan_area, :scan_frame, :combo_libraries]
@@ -71,14 +79,9 @@ module Alexandria
71
79
 
72
80
  def book_in_library(isbn10, library)
73
81
  isbn13 = Library.canonicalise_ean(isbn10)
74
- # puts "new book #{isbn10} (or #{isbn13})"
75
82
  match = library.find do |book|
76
- # puts "testing #{book.isbn}"
77
83
  (book.isbn == isbn10 || book.isbn == isbn13)
78
- # puts "book #{book.isbn}"
79
- # book == new_book
80
84
  end
81
- # puts "book_in_library match #{match.inspect}"
82
85
  !match.nil?
83
86
  rescue StandardError
84
87
  log.warn { "Failed to check for book #{isbn10} in library #{library}" }
@@ -92,7 +95,7 @@ module Alexandria
92
95
  library, is_new_library =
93
96
  @combo_libraries.selection_from_libraries(libraries)
94
97
 
95
- # NOTE at this stage, the ISBN is 10-digit...
98
+ # NOTE: at this stage, the ISBN is 10-digit...
96
99
  #
97
100
 
98
101
  selection = @barcodes_treeview.selection
@@ -358,14 +361,7 @@ module Alexandria
358
361
  Thread.new do
359
362
  pixbuf = nil
360
363
  if cover_uri
361
- image_data = nil
362
- if URI.parse(cover_uri).scheme.nil?
363
- File.open(cover_uri, "r") do |io|
364
- image_data = io.read
365
- end
366
- else
367
- image_data = URI.parse(cover_uri).read
368
- end
364
+ image_data = fetch_image(cover_uri)
369
365
  loader = GdkPixbuf::PixbufLoader.new
370
366
  loader.last_write(image_data)
371
367
  pixbuf = loader.pixbuf
@@ -472,7 +468,7 @@ module Alexandria
472
468
  if @scanner.match? @scanner_buffer
473
469
 
474
470
  Thread.new(@interval, @scanner_buffer) do |interval, buffer|
475
- log.debug { "Waiting for more scanner input..." }
471
+ log.debug { "Waiting for more scanner input" }
476
472
  GLib::Idle.add do
477
473
  @animation.manual_input
478
474
  false
@@ -492,7 +488,7 @@ module Alexandria
492
488
 
493
489
  else
494
490
  log.debug do
495
- "Buffer has changed while waiting, reading more characters..."
491
+ "Buffer has changed while waiting; reading more characters"
496
492
  end
497
493
  end
498
494
  end
@@ -511,12 +507,12 @@ module Alexandria
511
507
 
512
508
  def play_sound(effect)
513
509
  if effect == "scanning"
514
- puts "Effect: #{effect}, playing: #{@prefs.play_scanning_sound}" if $DEBUG
510
+ log.debug { "Effect: #{effect}, playing: #{@prefs.play_scanning_sound}" }
515
511
  return unless @prefs.play_scanning_sound
516
512
 
517
513
  @sound_players["scanning"].play("scanning")
518
514
  else
519
- puts "Effect: #{effect}, playing: #{@prefs.play_scan_sound}" if $DEBUG
515
+ log.debug { "Effect: #{effect}, playing: #{@prefs.play_scan_sound}" }
520
516
  return unless @prefs.play_scan_sound
521
517
 
522
518
  # sleep(0.5) # "scanning" effect lasts 0.5 seconds, wait for it to end
@@ -525,7 +521,7 @@ module Alexandria
525
521
  end
526
522
 
527
523
  def developer_test_scan
528
- log.info { "Developer test scan." }
524
+ log.info { "Developer test scan" }
529
525
  scans = [".C3nZC3nZC3n2ChnWENz7DxnY.cGen.ENr7C3j3C3f1Dxj3Dq.",
530
526
  ".C3nZC3nZC3n2ChnWENz7DxnY.cGen.ENr7C3z0CNj3Dhj1EW.",
531
527
  ".C3nZC3nZC3n2ChnWENz7DxnY.cGen.ENr7C3r2DNbXCxTZCW.",
@@ -4,22 +4,21 @@
4
4
  #
5
5
  # See the file README.md for authorship and licensing information.
6
6
 
7
+ require "cgi"
8
+
7
9
  module Alexandria
8
10
  module UI
9
- class AlertDialog < SimpleDelegator
11
+ class AlertDialog
10
12
  def initialize(parent, title, stock_icon, buttons, message = nil)
11
- dialog = Gtk::Dialog.new(title: "", parent: parent, flags: :destroy_with_parent,
12
- buttons: buttons)
13
- super(dialog)
14
-
15
- self.border_width = 6
16
- self.resizable = false
17
- child.spacing = 12
13
+ @dialog = Gtk::Dialog.new(title: "", parent: parent, flags: :destroy_with_parent,
14
+ buttons: buttons)
15
+ @dialog.border_width = 6
16
+ @dialog.resizable = false
17
+ @dialog.child.spacing = 12
18
18
 
19
19
  hbox = Gtk::Box.new(:horizontal, 12)
20
20
  hbox.homogeneous = false
21
21
  hbox.border_width = 6
22
- child.pack_start(hbox)
23
22
 
24
23
  image = Gtk::Image.new(stock: stock_icon,
25
24
  size: Gtk::IconSize::DIALOG)
@@ -28,21 +27,39 @@ module Alexandria
28
27
 
29
28
  vbox = Gtk::Box.new(:vertical, 6)
30
29
  vbox.homogeneous = false
30
+ vbox.pack_start make_label("<b><big>#{title}</big></b>")
31
+ vbox.pack_start make_label CGI.escapeHTML(message.strip) if message
31
32
  hbox.pack_start(vbox)
32
33
 
34
+ @dialog.child.pack_start(hbox)
35
+ end
36
+
37
+ def show_all
38
+ dialog.show_all
39
+ end
40
+
41
+ def run
42
+ dialog.run
43
+ end
44
+
45
+ def destroy
46
+ dialog.destroy
47
+ end
48
+
49
+ def default_response=(response)
50
+ dialog.default_response = response
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :dialog
56
+
57
+ def make_label(markup)
33
58
  label = Gtk::Label.new
34
59
  label.set_alignment(0, 0)
35
60
  label.wrap = label.selectable = true
36
- label.markup = "<b><big>#{title}</big></b>"
37
- vbox.pack_start(label)
38
-
39
- if message
40
- label = Gtk::Label.new
41
- label.set_alignment(0, 0)
42
- label.wrap = label.selectable = true
43
- label.markup = message.strip
44
- vbox.pack_start(label)
45
- end
61
+ label.markup = markup
62
+ label
46
63
  end
47
64
  end
48
65
  end