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
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ require "zoom"
8
+ require "alexandria/book_providers/pseudomarc"
9
+ require "marc"
10
+
11
+ module Alexandria
12
+ class BookProviders
13
+ class Z3950Provider < AbstractProvider
14
+ include Logging
15
+ include GetText
16
+ GetText.bindtextdomain(Alexandria::TEXTDOMAIN, charset: "UTF-8")
17
+
18
+ def initialize(name = "Z3950", fullname = "Z39.50")
19
+ super
20
+ prefs.add("hostname", _("Hostname"), "")
21
+ prefs.add("port", _("Port"), 7090)
22
+ prefs.add("database", _("Database"), "")
23
+ prefs.add("record_syntax", _("Record syntax"), "USMARC",
24
+ ["USMARC", "UNIMARC", "SUTRS"])
25
+ prefs.add("username", _("Username"), "", nil, false)
26
+ prefs.add("password", _("Password"), "", nil, false)
27
+ prefs.add("charset", _("Charset encoding"), "ISO-8859-1")
28
+
29
+ # HACK : piggybacking support
30
+ prefs.add("piggyback", "Piggyback", true, [true, false])
31
+ prefs.read
32
+ end
33
+
34
+ def search(criterion, type)
35
+ prefs.read
36
+ criterion = criterion.encode(prefs["charset"])
37
+
38
+ isbn = type == SEARCH_BY_ISBN ? criterion : nil
39
+ criterion = canonicalise_criterion(criterion, type)
40
+ conn_count = request_count(type)
41
+ resultset = search_records(criterion, type, conn_count)
42
+ log.debug { "total #{resultset.length}" }
43
+
44
+ results = books_from_resultset(resultset, isbn)
45
+ raise NoResultsError if results.empty?
46
+
47
+ type == SEARCH_BY_ISBN ? results.first : results
48
+ end
49
+
50
+ def url(_book)
51
+ nil
52
+ end
53
+
54
+ def books_from_resultset(resultset, isbn)
55
+ case prefs["record_syntax"]
56
+ when /MARC$/
57
+ books_from_marc(resultset, isbn)
58
+ when "SUTRS"
59
+ books_from_sutrs(resultset)
60
+ else
61
+ raise NoResultsError
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def request_count(type)
68
+ type == SEARCH_BY_ISBN ? 1 : 10 # results to retrieve
69
+ end
70
+
71
+ def canonicalise_criterion(criterion, type)
72
+ criterion = Library.canonicalise_isbn(criterion) if type == SEARCH_BY_ISBN
73
+ criterion
74
+ end
75
+
76
+ def books_from_sutrs(_resultset)
77
+ # SUTRS needs to be decoded separately, because each Z39.50 server has a
78
+ # different one.
79
+ raise NoResultsError
80
+ end
81
+
82
+ def marc_to_book(marc_txt, isbn)
83
+ begin
84
+ marc = MARC::Record.new_from_marc(marc_txt, forgiving: true)
85
+ rescue StandardError => ex
86
+ log.error { ex.message }
87
+ log.error { ex.backtrace.join("> \n") }
88
+ begin
89
+ marc = MARC::Record.new(marc_txt)
90
+ rescue StandardError => ex
91
+ log.error { ex.message }
92
+ log.error { ex.backtrace.join("> \n") }
93
+ raise ex
94
+ end
95
+ end
96
+
97
+ log.debug do
98
+ msg = "Parsing MARC"
99
+ msg += "\n title: #{marc.title}"
100
+ msg += "\n authors: #{marc.authors.join(', ')}"
101
+ msg += "\n isbn: #{marc.isbn}, #{isbn}"
102
+ msg += "\n publisher: #{marc.publisher}"
103
+ msg += "\n publish year: #{marc.publish_year}" if marc.respond_to?(:publish_year)
104
+ msg += "\n edition: #{marc.edition}"
105
+ msg
106
+ end
107
+
108
+ return if marc.title.nil? # or marc.authors.empty?
109
+
110
+ isbn ||= marc.isbn
111
+ isbn = Library.canonicalise_ean(isbn)
112
+
113
+ Book.new(marc.title, marc.authors,
114
+ isbn,
115
+ (marc.publisher || ""),
116
+ marc.respond_to?(:publish_year) ? marc.publish_year.to_i : nil,
117
+ (marc.edition || ""))
118
+ end
119
+
120
+ def books_from_marc(resultset, isbn)
121
+ results = []
122
+ resultset[0..9].each do |record|
123
+ marc_txt = record.render(prefs["charset"], "UTF-8")
124
+ log.debug { marc_txt }
125
+ File.binwrite(",marc.txt", marc_txt) if $DEBUG
126
+ book = nil
127
+ begin
128
+ mappings = Alexandria::PseudoMarcParser::USMARC_MAPPINGS
129
+ if prefs["hostname"] == "z3950.bnf.fr"
130
+ mappings = Alexandria::PseudoMarcParser::BNF_FR_MAPPINGS
131
+ end
132
+ # try pseudo-marc parser first (it seems to have more luck)
133
+ book = Alexandria::PseudoMarcParser.marc_text_to_book(marc_txt,
134
+ mappings)
135
+ if book.nil?
136
+ # failing that, try the genuine MARC parser
137
+ book = marc_to_book(marc_txt, isbn)
138
+ end
139
+ rescue StandardError => ex
140
+ log.warn { ex }
141
+ log.warn { ex.backtrace }
142
+ end
143
+
144
+ results << [book] unless book.nil?
145
+ end
146
+ results
147
+ end
148
+
149
+ def marc?
150
+ prefs["record_syntax"].end_with?("MARC")
151
+ end
152
+
153
+ def search_records(criterion, type, conn_count)
154
+ options = {}
155
+ unless prefs["username"].empty? || prefs["password"].empty?
156
+ options["user"] = prefs["username"]
157
+ options["password"] = prefs["password"]
158
+ end
159
+ hostname = prefs["hostname"]
160
+ port = prefs["port"].to_i
161
+ log.debug { "hostname #{hostname} port #{port} options #{options}" }
162
+ conn = ZOOM::Connection.new(options).connect(hostname, port)
163
+ conn.database_name = prefs["database"]
164
+
165
+ conn.preferred_record_syntax = prefs["record_syntax"]
166
+ conn.element_set_name = "F"
167
+ conn.count = conn_count
168
+ attr = case type
169
+ when SEARCH_BY_ISBN then [7]
170
+ when SEARCH_BY_TITLE then [4]
171
+ when SEARCH_BY_AUTHORS then [1, 1003]
172
+ when SEARCH_BY_KEYWORD then [1016]
173
+ end
174
+ pqf = ""
175
+ attr.each { |att| pqf += "@attr 1=#{att} " }
176
+ pqf += '"' + criterion.upcase + '"'
177
+ log.debug { "pqf is #{pqf}, syntax #{prefs['record_syntax']}" }
178
+
179
+ begin
180
+ if prefs.variable_named("piggyback") && !prefs["piggyback"]
181
+ log.debug { "setting conn.piggyback to false" }
182
+ conn.piggyback = false
183
+ end
184
+ conn.search(pqf)
185
+ rescue StandardError => ex
186
+ if /1005/.match?(ex.message) &&
187
+ prefs.variable_named("piggyback") && prefs["piggyback"]
188
+ log.error { "Z39.50 search failed:: #{ex.message}" }
189
+ log.info { "Turning off piggybacking for this provider" }
190
+ prefs.variable_named("piggyback").new_value = false
191
+ retry
192
+ end
193
+
194
+ raise ex
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -35,21 +35,26 @@ module Alexandria
35
35
  SEARCH_BY_KEYWORD = (0..3).to_a
36
36
 
37
37
  class SearchError < StandardError; end
38
+
38
39
  class NoResultsError < SearchError; end
39
- class ProviderSkippedError < NoResultsError; end # not an error :^(
40
- class SearchEmptyError < SearchError; end # sigh, again not really an error
40
+
41
41
  class TooManyResultsError < SearchError; end
42
+
42
43
  class InvalidSearchTypeError < SearchError; end
43
44
 
45
+ # These errors are not really errors
46
+ class ProviderSkippedError < NoResultsError; end
47
+
48
+ class SearchEmptyError < SearchError; end
49
+
44
50
  def self.search(criterion, type)
45
51
  factory_n = 0
46
- # puts "book_providers search #{self.instance.count_observers}"
47
52
 
48
53
  begin
49
54
  factory = instance[factory_n]
50
- puts factory.fullname + " lookup" if $DEBUG
55
+ log.debug { factory.fullname + " lookup" }
51
56
  unless factory.enabled
52
- puts factory.fullname + " disabled!, skipping..." if $DEBUG
57
+ log.debug { factory.fullname + " disabled!, skipping..." }
53
58
  raise ProviderSkippedError
54
59
  end
55
60
  instance.changed
@@ -83,7 +88,7 @@ module Alexandria
83
88
  trace = ex.backtrace.join("\n >")
84
89
  log.warn { "Provider #{factory.name} encountered error: #{ex.message} #{trace}" }
85
90
  end
86
- if last == factory
91
+ if factory == instance.last
87
92
  log.warn { "Error while searching #{criterion}" }
88
93
  message = case ex
89
94
  when Timeout::Error
@@ -92,14 +97,9 @@ module Alexandria
92
97
 
93
98
  when SocketError
94
99
  format(_("Couldn't reach the provider '%s': socket " \
95
- "error (%s)."), factory.name, ex.message)
96
-
97
- when NoResultsError
98
- _("No results were found. Make sure your " \
99
- "search criterion is spelled correctly, and " \
100
- "try again.")
100
+ "error (%s)."), factory.name, ex.message)
101
101
 
102
- when ProviderSkippedError
102
+ when NoResultsError, ProviderSkippedError
103
103
  _("No results were found. Make sure your " \
104
104
  "search criterion is spelled correctly, and " \
105
105
  "try again.")
@@ -113,8 +113,8 @@ module Alexandria
113
113
  else
114
114
  ex.message
115
115
  end
116
- puts "raising empty error #{message}"
117
- raise SearchEmptyError, message
116
+ log.debug { "raising empty error #{message}" }
117
+ raise SearchEmptyError, message # rubocop:disable I18n/GetText/DecorateFunctionMessage
118
118
  else
119
119
  factory_n += 1
120
120
  retry
@@ -152,9 +152,8 @@ module Alexandria
152
152
  end
153
153
 
154
154
  def new_value=(new_value)
155
- message = @provider.variable_name(self) + "="
156
- Alexandria::Preferences.instance.send(message,
157
- new_value)
155
+ name = @provider.variable_name(self)
156
+ Alexandria::Preferences.instance.set_variable(name, new_value)
158
157
  self.value = new_value
159
158
  end
160
159
 
@@ -187,8 +186,8 @@ module Alexandria
187
186
 
188
187
  def read
189
188
  each do |var|
190
- message = @provider.variable_name(var)
191
- val = Alexandria::Preferences.instance.send(message)
189
+ name = @provider.variable_name(var)
190
+ val = Alexandria::Preferences.instance.get_variable(name)
192
191
  var.value = val unless val.nil? || ((val == "") && var.mandatory?)
193
192
  end
194
193
  end
@@ -216,22 +215,22 @@ module Alexandria
216
215
  end
217
216
 
218
217
  def reinitialize(fullname)
219
- @name << "_" << fullname.hash.to_s
218
+ @name = "#{name}_#{fullname.hash}"
220
219
  @fullname = fullname
221
220
  prefs = Alexandria::Preferences.instance
222
- ary = prefs.abstract_providers
221
+ ary = prefs.get_variable :abstract_providers
223
222
  ary ||= []
224
223
  ary << @name
225
- prefs.abstract_providers = ary
226
- message = variable_name("name") + "="
227
- prefs.send(message, @fullname)
224
+ prefs.set_variable :abstract_providers, ary
225
+ message = variable_name("name")
226
+ prefs.set_variable(message, @fullname)
228
227
  end
229
228
 
230
229
  def remove
231
230
  prefs = Alexandria::Preferences.instance
232
- if (ary = prefs.abstract_providers)
231
+ if (ary = prefs.get_variable :abstract_providers)
233
232
  ary.delete(@name)
234
- prefs.abstract_providers = ary
233
+ prefs.set_variable :abstract_providers, ary
235
234
  end
236
235
  if (ary = prefs.providers_priority) && ary.include?(@name)
237
236
  ary.delete(@name)
@@ -271,8 +270,8 @@ module Alexandria
271
270
  !included_modules.include?(Singleton)
272
271
  end
273
272
 
274
- def <=>(provider)
275
- fullname <=> provider.fullname
273
+ def <=>(other)
274
+ fullname <=> other.fullname
276
275
  end
277
276
 
278
277
  # FIXME: Clean up this complex abstract/concrete class system
@@ -291,19 +290,14 @@ module Alexandria
291
290
 
292
291
  require "alexandria/book_providers/douban" # only requires YAML
293
292
 
294
- # Amazon AWS (Amazon Associates Web Services) provider, needs hpricot
295
- require "alexandria/book_providers/amazon_aws"
296
-
297
293
  # Website based providers
298
- require "alexandria/book_providers/adlibris"
299
- require "alexandria/book_providers/barnes_and_noble"
300
- require "alexandria/book_providers/proxis"
301
- require "alexandria/book_providers/siciliano"
302
- require "alexandria/book_providers/thalia"
294
+ require "alexandria/book_providers/thalia_provider"
303
295
  require "alexandria/book_providers/worldcat"
304
296
 
305
297
  # Z39.50 based providers
306
- require "alexandria/book_providers/z3950"
298
+ require "alexandria/book_providers/loc_provider"
299
+ require "alexandria/book_providers/bl_provider"
300
+ require "alexandria/book_providers/sbn_provider"
307
301
 
308
302
  attr_reader :abstract_classes
309
303
 
@@ -325,10 +319,9 @@ module Alexandria
325
319
  next unless md
326
320
 
327
321
  klass = self.class.module_eval(constant.to_s)
328
- if klass.ancestors.include?(AbstractProvider) &&
322
+ if klass < AbstractProvider &&
329
323
  (klass != GenericProvider) &&
330
- (klass != WebsiteBasedProvider) &&
331
- (klass != AbstractProvider)
324
+ (klass != WebsiteBasedProvider)
332
325
 
333
326
  if klass.abstract?
334
327
  @abstract_classes << klass
@@ -337,7 +330,7 @@ module Alexandria
337
330
  end
338
331
  end
339
332
  end
340
- if (ary = @prefs.abstract_providers)
333
+ if (ary = @prefs.get_variable :abstract_providers)
341
334
  ary.each do |name|
342
335
  md = /^(.+)_/.match(name)
343
336
  next unless md
@@ -366,37 +359,27 @@ module Alexandria
366
359
  compact!
367
360
  end
368
361
 
369
- # FIXME: Define the handful of methods that use this.
370
- def self.method_missing(id, *args, &block)
371
- if instance.respond_to? id
372
- instance.method(id).call(*args, &block)
373
- else
374
- super
375
- end
362
+ def self.list
363
+ instance
364
+ end
365
+
366
+ def self.abstract_classes
367
+ instance.abstract_classes
376
368
  end
377
369
 
378
370
  private
379
371
 
380
372
  def rejig_providers_priority
381
373
  priority = (@prefs.providers_priority || [])
382
- unless priority.empty?
383
- changed = false
374
+ return if priority.empty?
384
375
 
385
- if (ecs_index = priority.index("AmazonECS"))
386
- priority[ecs_index] = "Amazon" # replace legacy "AmazonECS" name
387
- priority.uniq! # remove any other "Amazon" from the list
388
- changed = true
389
- end
390
- if (worldcat_index = priority.index("Worldcat"))
391
- priority[worldcat_index] = "WorldCat"
392
- changed = true
393
- end
394
- if (adlibris_index = priority.index("Adlibris"))
395
- priority[adlibris_index] = "AdLibris"
396
- changed = true
397
- end
398
- @prefs.providers_priority = priority if changed
376
+ changed = false
377
+
378
+ if (worldcat_index = priority.index("Worldcat"))
379
+ priority[worldcat_index] = "WorldCat"
380
+ changed = true
399
381
  end
382
+ @prefs.providers_priority = priority if changed
400
383
  end
401
384
  end
402
385
  end
@@ -11,6 +11,7 @@ module Alexandria
11
11
  "view_as" => 0,
12
12
  "arrange_icons_mode" => 0,
13
13
  "reverse_icons" => false,
14
+ "selected_library" => "",
14
15
  "barcode_scanner" => "CueCat",
15
16
  "play_scanning_sound" => true,
16
17
  "play_scan_sound" => true,
@@ -29,7 +30,7 @@ module Alexandria
29
30
  "col_want_visible" => true,
30
31
  "col_tags_visible" => true,
31
32
  "cols_width" => "{}",
32
- "providers_priority" => ["Amazon", "BarnesAndNoble", "AdLibris", "Proxis", "Thalia", "Siciliano", "WorldCat", "LOC", "BL", "SBN"],
33
+ "providers_priority" => ["Thalia", "WorldCat", "LOC", "BL", "SBN"],
33
34
  "view_advanced_settings" => false
34
35
  }
35
36
  end
@@ -63,10 +63,11 @@ module Alexandria
63
63
 
64
64
  id, procedure, args, need_retval = ary
65
65
  retval = procedure.call(*args)
66
- if need_retval
67
- @protect_pending_retvals.synchronize do
68
- @pending_retvals << [id, retval]
69
- end
66
+
67
+ return unless need_retval
68
+
69
+ @protect_pending_retvals.synchronize do
70
+ @pending_retvals << [id, retval]
70
71
  end
71
72
  end
72
73
 
@@ -81,14 +82,14 @@ module Alexandria
81
82
  @id += 1
82
83
  @pending_calls << [@id, procedure, args, need_retval]
83
84
  end
84
- if need_retval
85
- loop do
86
- @protect_pending_retvals.synchronize do
87
- ary = @pending_retvals.find { |id, _retval| id == @id }
88
- if ary
89
- @pending_retvals.delete(ary)
90
- return ary[1]
91
- end
85
+ return unless need_retval
86
+
87
+ loop do
88
+ @protect_pending_retvals.synchronize do
89
+ ary = @pending_retvals.find { |id, _retval| id == @id }
90
+ if ary
91
+ @pending_retvals.delete(ary)
92
+ return ary[1]
92
93
  end
93
94
  end
94
95
  end
@@ -36,25 +36,24 @@ module Alexandria
36
36
  end
37
37
 
38
38
  def export_as_onix_xml_archive(filename)
39
- File.open(File.join(Dir.tmpdir, "onix.xml"), "w") do |io|
39
+ dir = Dir.mktmpdir
40
+ File.open(File.join(dir, "onix.xml"), "w") do |io|
40
41
  to_onix_document.write(io, 0)
41
42
  end
42
- copy_covers(File.join(Dir.tmpdir, "images"))
43
- Dir.chdir(Dir.tmpdir) do
43
+ copy_covers(File.join(dir, "images"))
44
+ Dir.chdir(dir) do
44
45
  output = `tar -cjf \"#{filename}\" onix.xml images 2>&1`
45
46
  raise output unless $CHILD_STATUS.success?
46
47
  end
47
- FileUtils.rm_rf(File.join(Dir.tmpdir, "images"))
48
- FileUtils.rm(File.join(Dir.tmpdir, "onix.xml"))
48
+ FileUtils.rm_rf(File.join(dir, "images"))
49
+ FileUtils.rm(File.join(dir, "onix.xml"))
50
+ ensure
51
+ FileUtils.remove_entry dir
49
52
  end
50
53
 
51
54
  def export_as_tellico_xml_archive(filename)
52
55
  File.open(File.join(Dir.tmpdir, "tellico.xml"), "w") do |io|
53
56
  to_tellico_document.write(io, 0)
54
- rescue StandardError => ex
55
- puts ex.message
56
- puts ex.backtrace
57
- raise ex
58
57
  end
59
58
  copy_covers(File.join(Dir.tmpdir, "images"))
60
59
  Dir.chdir(Dir.tmpdir) do
@@ -190,7 +189,7 @@ module Alexandria
190
189
  File.join("images", final_cover(book))
191
190
  end
192
191
  if book.isbn
193
- BookProviders.each do |provider|
192
+ BookProviders.list.each do |provider|
194
193
  elem = prod.add_element("ProductWebsite")
195
194
  elem.add_element("ProductWebsiteDescription").text =
196
195
  provider.fullname
@@ -252,7 +251,7 @@ module Alexandria
252
251
  entry.add_element("cover").text = final_cover(book)
253
252
  image = images.add_element("image")
254
253
  image.add_attribute("id", final_cover(book))
255
- image_s = ImageSize.new(IO.read(cover(book)))
254
+ image_s = ImageSize.new(File.read(cover(book)))
256
255
  image.add_attribute("height", image_s.height.to_s)
257
256
  image.add_attribute("width", image_s.width.to_s)
258
257
  image.add_attribute("format", image_s.format)
@@ -267,7 +266,7 @@ module Alexandria
267
266
  escaped.gsub!(/&/, "&amp;")
268
267
  escaped.gsub!(/</, "&lt;")
269
268
  escaped.gsub!(/>/, "&gt;")
270
- escaped.gsub!(/\"/, "&quot;")
269
+ escaped.gsub!(/"/, "&quot;")
271
270
  escaped
272
271
  end
273
272
 
@@ -299,7 +298,7 @@ module Alexandria
299
298
  EOS
300
299
 
301
300
  if File.exist?(cover(book))
302
- image_s = ImageSize.new(IO.read(cover(book)))
301
+ image_s = ImageSize.new(File.read(cover(book)))
303
302
  xhtml << <<~EOS
304
303
  <img class="book_cover"
305
304
  src="#{File.join('pixmaps', final_cover(book))}"
@@ -375,7 +374,7 @@ module Alexandria
375
374
  bibtex << 'author = "'
376
375
  if book.authors != []
377
376
  bibtex << book.authors[0]
378
- book.authors[1..-1].each do |author|
377
+ book.authors[1..].each do |author|
379
378
  bibtex << " and #{latex_escape(author)}"
380
379
  end
381
380
  end
@@ -396,15 +395,15 @@ module Alexandria
396
395
  return "" if str.nil?
397
396
 
398
397
  my_str = str.dup
399
- my_str.gsub!(/%/, '\\%')
400
- my_str.gsub!(/~/, '\\textasciitilde')
401
- my_str.gsub!(/\&/, '\\\\&')
402
- my_str.gsub!(/\#/, '\\\\#')
403
- my_str.gsub!(/\{/, '\\{')
404
- my_str.gsub!(/\}/, '\\}')
405
- my_str.gsub!(/_/, '\\_')
398
+ my_str.gsub!(/%/, "\\%")
399
+ my_str.gsub!(/~/, "\\textasciitilde")
400
+ my_str.gsub!(/&/, "\\\\&")
401
+ my_str.gsub!(/\#/, "\\\\#")
402
+ my_str.gsub!(/\{/, "\\{")
403
+ my_str.gsub!(/\}/, "\\}")
404
+ my_str.gsub!(/_/, "\\_")
406
405
  my_str.gsub!(/\$/, "\\\$")
407
- my_str.gsub!(/\"(.+)\"/, "``\1''")
406
+ my_str.gsub!(/"(.+)"/, "``\1''")
408
407
  my_str
409
408
  end
410
409
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of Alexandria.
4
+ #
5
+ # See the file README.md for authorship and licensing information.
6
+
7
+ require "alexandria/net"
8
+
9
+ module Alexandria
10
+ module ImageFetcher
11
+ def fetch_image(uri)
12
+ result = nil
13
+ if URI.parse(uri).scheme.nil?
14
+ File.open(uri, "r") do |io|
15
+ result = io.read
16
+ end
17
+ else
18
+ result = WWWAgent.new.get(uri)
19
+ end
20
+ result
21
+ rescue Errno::ECONNRESET
22
+ nil
23
+ end
24
+ end
25
+ end