alexandria-book-collection-manager 0.6.9 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +34 -30
- data/.rubocop_todo.yml +139 -54
- data/CHANGELOG.md +10 -0
- data/Gemfile +1 -0
- data/Rakefile +10 -11
- data/alexandria-book-collection-manager.gemspec +3 -2
- data/bin/alexandria +1 -1
- data/lib/alexandria.rb +3 -6
- data/lib/alexandria/about.rb +9 -9
- data/lib/alexandria/book_providers.rb +12 -12
- data/lib/alexandria/book_providers/adlibris.rb +14 -18
- data/lib/alexandria/book_providers/amazon_aws.rb +17 -31
- data/lib/alexandria/book_providers/amazon_ecs_util.rb +5 -6
- data/lib/alexandria/book_providers/barnes_and_noble.rb +51 -76
- data/lib/alexandria/book_providers/bol_it.rb +12 -12
- data/lib/alexandria/book_providers/deastore.rb +27 -31
- data/lib/alexandria/book_providers/douban.rb +9 -13
- data/lib/alexandria/book_providers/ibs_it.rb +10 -10
- data/lib/alexandria/book_providers/mcu.rb +12 -18
- data/lib/alexandria/book_providers/proxis.rb +14 -22
- data/lib/alexandria/book_providers/pseudomarc.rb +8 -18
- data/lib/alexandria/book_providers/renaud.rb +16 -16
- data/lib/alexandria/book_providers/siciliano.rb +25 -38
- data/lib/alexandria/book_providers/thalia.rb +13 -16
- data/lib/alexandria/book_providers/webster_it.rb +14 -18
- data/lib/alexandria/book_providers/worldcat.rb +21 -25
- data/lib/alexandria/book_providers/z3950.rb +19 -23
- data/lib/alexandria/config.rb +2 -2
- data/lib/alexandria/execution_queue.rb +3 -1
- data/lib/alexandria/export_library.rb +19 -22
- data/lib/alexandria/import_library.rb +14 -18
- data/lib/alexandria/import_library_csv.rb +12 -30
- data/lib/alexandria/models/book.rb +7 -9
- data/lib/alexandria/models/library.rb +44 -44
- data/lib/alexandria/net.rb +1 -1
- data/lib/alexandria/preferences.rb +12 -57
- data/lib/alexandria/scanners.rb +10 -6
- data/lib/alexandria/scanners/cuecat.rb +2 -2
- data/lib/alexandria/smart_library.rb +12 -12
- data/lib/alexandria/ui.rb +5 -2
- data/lib/alexandria/ui/callbacks.rb +106 -65
- data/lib/alexandria/ui/completion_models.rb +55 -51
- data/lib/alexandria/ui/dialogs/about_dialog.rb +1 -1
- data/lib/alexandria/ui/dialogs/acquire_dialog.rb +25 -51
- data/lib/alexandria/ui/dialogs/alert_dialog.rb +13 -11
- data/lib/alexandria/ui/dialogs/bad_isbns_dialog.rb +2 -2
- data/lib/alexandria/ui/dialogs/barcode_animation.rb +39 -23
- data/lib/alexandria/ui/dialogs/book_properties_dialog.rb +16 -21
- data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +23 -24
- data/lib/alexandria/ui/dialogs/export_dialog.rb +46 -45
- data/lib/alexandria/ui/dialogs/import_dialog.rb +26 -35
- data/lib/alexandria/ui/dialogs/misc_dialogs.rb +11 -11
- data/lib/alexandria/ui/dialogs/new_book_dialog.rb +47 -59
- data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +14 -13
- data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +12 -11
- data/lib/alexandria/ui/dialogs/preferences_dialog.rb +37 -43
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +9 -8
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +47 -53
- data/lib/alexandria/ui/dndable.rb +5 -4
- data/lib/alexandria/ui/icons.rb +19 -19
- data/lib/alexandria/ui/iconview.rb +7 -12
- data/lib/alexandria/ui/iconview_tooltips.rb +22 -109
- data/lib/alexandria/ui/init.rb +7 -15
- data/lib/alexandria/ui/libraries_combo.rb +54 -48
- data/lib/alexandria/ui/listview.rb +30 -85
- data/lib/alexandria/ui/multi_drag_treeview.rb +110 -107
- data/lib/alexandria/ui/sidepane.rb +23 -25
- data/lib/alexandria/ui/sound.rb +18 -27
- data/lib/alexandria/ui/ui_manager.rb +126 -204
- data/lib/alexandria/undo_manager.rb +2 -2
- data/lib/alexandria/version.rb +4 -4
- data/spec/alexandria/book_providers_spec.rb +7 -4
- data/spec/alexandria/library_spec.rb +13 -16
- data/spec/alexandria/scanners/cuecat_spec.rb +1 -2
- data/spec/alexandria/ui/dialogs_spec.rb +5 -1
- data/spec/alexandria/ui/main_app_spec.rb +3 -3
- data/{lib/alexandria/utils.rb → spec/alexandria/ui/sound_spec.rb} +6 -11
- data/spec/alexandria/ui/ui_utilities_spec.rb +3 -3
- data/spec/spec_helper.rb +2 -2
- data/util/rake/fileinstall.rb +17 -33
- data/util/rake/gettextgenerate.rb +2 -4
- data/util/rake/omfgenerate.rb +1 -3
- metadata +23 -11
- data/lib/alexandria/ui/gtk_thread_help.rb +0 -89
@@ -46,11 +46,12 @@ Gem::Specification.new do |s|
|
|
46
46
|
s.add_runtime_dependency('gettext', ['~> 3.1'])
|
47
47
|
s.add_runtime_dependency('hpricot', ['~> 0.8.5'])
|
48
48
|
s.add_runtime_dependency('htmlentities', ['~> 4.3'])
|
49
|
-
s.add_runtime_dependency('
|
49
|
+
s.add_runtime_dependency('gtk3', ['~> 3.0.9'])
|
50
|
+
s.add_runtime_dependency('gio2', ['~> 3.0.9'])
|
50
51
|
s.add_runtime_dependency('gstreamer', ['~> 3.0'])
|
51
52
|
|
52
53
|
s.add_development_dependency('minitest', ['~> 5.0'])
|
53
|
-
s.add_development_dependency('rake', ['~>
|
54
|
+
s.add_development_dependency('rake', ['~> 11.1'])
|
54
55
|
s.add_development_dependency('rspec', ['~> 3.0'])
|
55
56
|
|
56
57
|
s.require_paths = ['lib']
|
data/bin/alexandria
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# Copyright (C) 2004-2006 Laurent Sansonetti
|
4
4
|
# Copyright (C) 2007, 2008 Joseph Method
|
5
5
|
# Copyright (C) 2007, 2009 Cathal Mc Ginley
|
6
|
-
# Copyright (C) 2014 Matijs van Zuijlen
|
6
|
+
# Copyright (C) 2014, 2015 Matijs van Zuijlen
|
7
7
|
#
|
8
8
|
# Alexandria is free software; you can redistribute it and/or
|
9
9
|
# modify it under the terms of the GNU General Public License as
|
data/lib/alexandria.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Copyright (C) 2004-2006 Laurent Sansonetti
|
2
|
-
# Copyright (C) 2011 Matijs van Zuijlen
|
2
|
+
# Copyright (C) 2011, 2016 Matijs van Zuijlen
|
3
3
|
#
|
4
4
|
# Alexandria is free software; you can redistribute it and/or
|
5
5
|
# modify it under the terms of the GNU General Public License as
|
@@ -34,13 +34,11 @@ require 'alexandria/about'
|
|
34
34
|
|
35
35
|
module Alexandria
|
36
36
|
def self.set_proxy
|
37
|
-
ENV['http_proxy'] = nil if !ENV['http_proxy'].nil?
|
37
|
+
ENV['http_proxy'] = nil if !ENV['http_proxy'].nil? && URI.parse(ENV['http_proxy']).userinfo.nil?
|
38
38
|
end
|
39
39
|
|
40
40
|
def self.set_log_level
|
41
|
-
if $DEBUG
|
42
|
-
Alexandria.log.level = Logger::DEBUG
|
43
|
-
end
|
41
|
+
Alexandria.log.level = Logger::DEBUG if $DEBUG
|
44
42
|
Alexandria.log.debug { 'Initializing Alexandria...' }
|
45
43
|
end
|
46
44
|
|
@@ -64,7 +62,6 @@ require 'alexandria/version'
|
|
64
62
|
# VERSION = OSX::NSBundle.mainBundle.infoDictionary.objectForKey('CFBundleVersion').to_s
|
65
63
|
# end
|
66
64
|
# end
|
67
|
-
require 'alexandria/utils'
|
68
65
|
|
69
66
|
require 'alexandria/models/book'
|
70
67
|
require 'alexandria/models/library'
|
data/lib/alexandria/about.rb
CHANGED
@@ -19,13 +19,13 @@
|
|
19
19
|
# Fifth Floor, Boston, MA 02110-1301 USA.
|
20
20
|
|
21
21
|
module Alexandria
|
22
|
-
TITLE = 'Alexandria'
|
23
|
-
TEXTDOMAIN = 'alexandria'
|
22
|
+
TITLE = 'Alexandria'.freeze
|
23
|
+
TEXTDOMAIN = 'alexandria'.freeze
|
24
24
|
extend GetText
|
25
25
|
bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
|
26
26
|
DESCRIPTION = _('A program to help you manage your book collection.')
|
27
27
|
COPYRIGHT = "Copyright (C) 2004-2006 Laurent Sansonetti\n" \
|
28
|
-
'Copyright (C) 2007-2010,2014,2015 Alexandria Contributors'
|
28
|
+
'Copyright (C) 2007-2010,2014,2015 Alexandria Contributors'.freeze
|
29
29
|
AUTHORS = [
|
30
30
|
'Alexander McCormmach <alexander@tunicate.org>',
|
31
31
|
'Aymeric Nys <aymeric@nnx.com>',
|
@@ -48,11 +48,11 @@ module Alexandria
|
|
48
48
|
'Takayuki Kusano <AE5T-KSN@asahi-net.or.jp>',
|
49
49
|
'Timothy Malone <timothy.malone@gmail.com>',
|
50
50
|
'Zachary P. Landau <kapheine@hypa.net>'
|
51
|
-
]
|
51
|
+
].freeze
|
52
52
|
DOCUMENTERS = [
|
53
53
|
'Cathal Mc Ginley <cathal.alexandria@gnostai.org>',
|
54
54
|
'Liam Davison <registrations@liamjdavison.info>'
|
55
|
-
]
|
55
|
+
].freeze
|
56
56
|
TRANSLATORS = [
|
57
57
|
'Adrián Chaves Fernández <adriyetichaves@gmail.com> (gl)',
|
58
58
|
'Cathal Mc Ginley <cathal.alexandria@gnostai.org> (ga)',
|
@@ -72,11 +72,11 @@ module Alexandria
|
|
72
72
|
'Petr Vanek <vanous@penguin.cz> (cs)',
|
73
73
|
'Piotr Drąg <piotrdrag@gmail.com> (pl)',
|
74
74
|
'Serhij Dubyk <dubyk@library.lviv.ua> (uk)'
|
75
|
-
]
|
75
|
+
].freeze
|
76
76
|
ARTISTS = [
|
77
77
|
'Andreas Nilsson <nisses.mail@home.se>',
|
78
78
|
'Stefanie Dijoux <stefanie.dijoux@gmail.com>'
|
79
|
-
]
|
80
|
-
BUGREPORT_URL = 'http://www.github.com/mvz/alexandria-book-collection-manager/issues'
|
81
|
-
WEBSITE_URL = 'http://www.github.com/mvz/alexandria-book-collection-manager'
|
79
|
+
].freeze
|
80
|
+
BUGREPORT_URL = 'http://www.github.com/mvz/alexandria-book-collection-manager/issues'.freeze
|
81
|
+
WEBSITE_URL = 'http://www.github.com/mvz/alexandria-book-collection-manager'.freeze
|
82
82
|
end
|
@@ -57,7 +57,7 @@ module Alexandria
|
|
57
57
|
# sanity check if at least one valid result is actually found
|
58
58
|
results.delete_if { |book, _cover| book.nil? }
|
59
59
|
|
60
|
-
if results.
|
60
|
+
if results.empty?
|
61
61
|
instance.changed
|
62
62
|
instance.notify_observers(:not_found, factory.fullname) # new
|
63
63
|
raise NoResultsError
|
@@ -187,7 +187,7 @@ module Alexandria
|
|
187
187
|
each do |var|
|
188
188
|
message = @provider.variable_name(var)
|
189
189
|
val = Alexandria::Preferences.instance.send(message)
|
190
|
-
var.value = val unless val.nil?
|
190
|
+
var.value = val unless val.nil? || ((val == '') && var.mandatory?)
|
191
191
|
end
|
192
192
|
end
|
193
193
|
end
|
@@ -199,7 +199,7 @@ module Alexandria
|
|
199
199
|
|
200
200
|
def initialize(name, fullname = nil)
|
201
201
|
@name = name
|
202
|
-
@fullname = (fullname
|
202
|
+
@fullname = (fullname || name)
|
203
203
|
@prefs = Preferences.new(self)
|
204
204
|
@prefs.add('enabled', _('Enabled'), true, [true, false])
|
205
205
|
end
|
@@ -231,7 +231,7 @@ module Alexandria
|
|
231
231
|
ary.delete(@name)
|
232
232
|
prefs.abstract_providers = ary
|
233
233
|
end
|
234
|
-
if (ary = prefs.providers_priority)
|
234
|
+
if (ary = prefs.providers_priority) && ary.include?(@name)
|
235
235
|
ary.delete(@name)
|
236
236
|
prefs.providers_priority = ary
|
237
237
|
end
|
@@ -266,7 +266,7 @@ module Alexandria
|
|
266
266
|
end
|
267
267
|
|
268
268
|
def self.abstract?
|
269
|
-
|
269
|
+
!included_modules.include?(Singleton)
|
270
270
|
end
|
271
271
|
|
272
272
|
def <=>(provider)
|
@@ -333,10 +333,10 @@ module Alexandria
|
|
333
333
|
md = /(.+)Provider$/.match(constant)
|
334
334
|
next unless md
|
335
335
|
klass = self.class.module_eval(constant.to_s)
|
336
|
-
if klass.ancestors.include?(AbstractProvider)
|
337
|
-
klass != GenericProvider
|
338
|
-
klass != WebsiteBasedProvider
|
339
|
-
klass != AbstractProvider
|
336
|
+
if klass.ancestors.include?(AbstractProvider) &&
|
337
|
+
(klass != GenericProvider) &&
|
338
|
+
(klass != WebsiteBasedProvider) &&
|
339
|
+
(klass != AbstractProvider)
|
340
340
|
|
341
341
|
if klass.abstract?
|
342
342
|
@abstract_classes << klass
|
@@ -363,12 +363,12 @@ module Alexandria
|
|
363
363
|
end
|
364
364
|
clear
|
365
365
|
rejig_providers_priority
|
366
|
-
priority = (@prefs.providers_priority
|
366
|
+
priority = (@prefs.providers_priority || [])
|
367
367
|
priority.map!(&:strip)
|
368
368
|
rest = providers.keys - priority
|
369
369
|
priority.each { |pname| self << providers[pname] }
|
370
370
|
rest.sort.each { |pname| self << providers[pname] }
|
371
|
-
|
371
|
+
compact!
|
372
372
|
end
|
373
373
|
|
374
374
|
# FIXME: Define the handful of methods that use this.
|
@@ -383,7 +383,7 @@ module Alexandria
|
|
383
383
|
private
|
384
384
|
|
385
385
|
def rejig_providers_priority
|
386
|
-
priority = (@prefs.providers_priority
|
386
|
+
priority = (@prefs.providers_priority || [])
|
387
387
|
unless priority.empty?
|
388
388
|
changed = false
|
389
389
|
|
@@ -33,12 +33,12 @@ module Alexandria
|
|
33
33
|
class AdLibrisProvider < WebsiteBasedProvider
|
34
34
|
include Alexandria::Logging
|
35
35
|
|
36
|
-
SITE = 'http://www.adlibris.com/se/'
|
36
|
+
SITE = 'http://www.adlibris.com/se/'.freeze
|
37
37
|
|
38
38
|
BASE_SEARCH_URL = "#{SITE}searchresult.aspx?search=advanced&%s=%s" \
|
39
|
-
'&fromproduct=False' # type/term
|
39
|
+
'&fromproduct=False'.freeze # type/term
|
40
40
|
|
41
|
-
PRODUCT_URL = "#{SITE}product.aspx?isbn=%s"
|
41
|
+
PRODUCT_URL = "#{SITE}product.aspx?isbn=%s".freeze
|
42
42
|
|
43
43
|
def initialize
|
44
44
|
super('AdLibris', 'AdLibris (Sweden)')
|
@@ -76,17 +76,17 @@ module Alexandria
|
|
76
76
|
if search_type == SEARCH_BY_ISBN
|
77
77
|
PRODUCT_URL % Library.canonicalise_isbn(search_term)
|
78
78
|
else
|
79
|
-
search_type_code = {
|
79
|
+
(search_type_code = {
|
80
80
|
SEARCH_BY_AUTHORS => 'author',
|
81
81
|
SEARCH_BY_TITLE => 'title',
|
82
82
|
SEARCH_BY_KEYWORD => 'keyword'
|
83
|
-
}[search_type]
|
83
|
+
}[search_type]) || 'keyword'
|
84
84
|
search_term_encoded = CGI.escape(search_term)
|
85
85
|
BASE_SEARCH_URL % [search_type_code, search_term_encoded]
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
# TODO use Iconv to pre-convert the html.body to UTF-8 everywhere
|
89
|
+
# TODO: use Iconv to pre-convert the html.body to UTF-8 everywhere
|
90
90
|
# before sending it to the parser methods
|
91
91
|
|
92
92
|
def get_book_from_search_result(rslt)
|
@@ -187,28 +187,24 @@ module Alexandria
|
|
187
187
|
|
188
188
|
isbn_tds.each do |isbn_td|
|
189
189
|
isbn = isbn_td.inner_text
|
190
|
-
unless isbn =~ /[0-9x]{10,13}/i
|
191
|
-
next
|
192
|
-
end
|
190
|
+
next unless isbn =~ /[0-9x]{10,13}/i
|
193
191
|
isbn.gsub(/(\n|\r)/, ' ')
|
194
192
|
if isbn =~ /:[\s]*([0-9x]+)/i
|
195
193
|
isbn = Regexp.last_match[1]
|
196
194
|
end
|
197
195
|
isbns << isbn
|
198
196
|
end
|
199
|
-
isbn =
|
200
|
-
if isbn
|
201
|
-
isbn = Library.canonicalise_isbn(isbn)
|
202
|
-
end
|
197
|
+
isbn = isbns.first
|
198
|
+
isbn = Library.canonicalise_isbn(isbn) if isbn
|
203
199
|
|
204
200
|
# cover
|
205
201
|
image_url = nil
|
206
202
|
if (cover_img = doc.search('span.imageWithShadow img[@id$="ProductImageNotLinked"]').first)
|
207
|
-
if cover_img['src'] =~ /^http\:\/\//
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
203
|
+
image_url = if cover_img['src'] =~ /^http\:\/\//
|
204
|
+
cover_img['src']
|
205
|
+
else
|
206
|
+
"#{SITE}/#{cover_img['src']}" # HACK: use html base
|
207
|
+
end
|
212
208
|
if image_url =~ /noimage.gif$/
|
213
209
|
# no point downloading a "no image" graphic
|
214
210
|
# Alexandria has its own generic book icon...
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Copyright (C) 2004-2006 Laurent Sansonetti
|
2
2
|
# Copyright (C) 2008 Cathal Mc Ginley
|
3
|
-
# Copyright (C) 2014 Matijs van Zuijlen
|
3
|
+
# Copyright (C) 2014, 2016 Matijs van Zuijlen
|
4
4
|
#
|
5
5
|
# Alexandria is free software; you can redistribute it and/or
|
6
6
|
# modify it under the terms of the GNU General Public License as
|
@@ -31,7 +31,7 @@ module Alexandria
|
|
31
31
|
|
32
32
|
# CACHE_DIR = File.join(Alexandria::Library::DIR, '.amazon_cache')
|
33
33
|
|
34
|
-
LOCALES = ['ca', 'de', 'fr', 'jp', 'uk', 'us']
|
34
|
+
LOCALES = ['ca', 'de', 'fr', 'jp', 'uk', 'us'].freeze
|
35
35
|
|
36
36
|
def initialize
|
37
37
|
super('Amazon', 'Amazon')
|
@@ -46,17 +46,17 @@ module Alexandria
|
|
46
46
|
# kill old (shorter) tokens, or previously distributed Access Key Id (see #26250)
|
47
47
|
|
48
48
|
if token
|
49
|
-
if
|
49
|
+
if token.value != token.value.strip
|
50
50
|
token.new_value = token.value.strip
|
51
51
|
end
|
52
52
|
end
|
53
|
-
if token
|
53
|
+
if token && ((token.value.size != 20) || (token.value == '0J356Z09CN88KB743582'))
|
54
54
|
token.new_value = ''
|
55
55
|
end
|
56
56
|
|
57
57
|
secret = prefs.variable_named('secret_key')
|
58
58
|
if secret
|
59
|
-
if
|
59
|
+
if secret.value != secret.value.strip
|
60
60
|
secret.new_value = secret.value.strip
|
61
61
|
end
|
62
62
|
end
|
@@ -66,7 +66,7 @@ module Alexandria
|
|
66
66
|
if associate.value.strip.empty?
|
67
67
|
associate.new_value = 'rubyalexa-20'
|
68
68
|
end
|
69
|
-
if
|
69
|
+
if associate.value != associate.value.strip
|
70
70
|
associate.new_value = associate.value.strip
|
71
71
|
end
|
72
72
|
end
|
@@ -76,13 +76,13 @@ module Alexandria
|
|
76
76
|
prefs.read
|
77
77
|
|
78
78
|
if prefs['secret_key'].empty?
|
79
|
-
raise Amazon::RequestError
|
79
|
+
raise Amazon::RequestError, 'Secret Access Key required for Authentication: you must sign up for your own Amazon AWS account'
|
80
80
|
end
|
81
81
|
|
82
82
|
if (config = Alexandria::Preferences.instance.http_proxy_config)
|
83
83
|
host, port, user, pass = config
|
84
84
|
url = 'http://'
|
85
|
-
url += user + ':' + pass + '@' if user
|
85
|
+
url += user + ':' + pass + '@' if user && pass
|
86
86
|
url += host + ':' + port.to_s
|
87
87
|
ENV['http_proxy'] = url
|
88
88
|
end
|
@@ -105,7 +105,7 @@ module Alexandria
|
|
105
105
|
when SEARCH_BY_ISBN
|
106
106
|
criterion = Library.canonicalise_isbn(criterion)
|
107
107
|
# This isn't ideal : I'd like to do an ISBN/EAN-specific search
|
108
|
-
res = Amazon::Ecs.item_search(criterion,
|
108
|
+
res = Amazon::Ecs.item_search(criterion, response_group: 'ItemAttributes,Images', country: request_locale)
|
109
109
|
res.items.each do |item|
|
110
110
|
products << item
|
111
111
|
end
|
@@ -129,10 +129,10 @@ module Alexandria
|
|
129
129
|
end
|
130
130
|
|
131
131
|
when SEARCH_BY_TITLE
|
132
|
-
res = Amazon::Ecs.item_search(criterion,
|
132
|
+
res = Amazon::Ecs.item_search(criterion, response_group: 'ItemAttributes,Images', country: request_locale)
|
133
133
|
|
134
134
|
res.items.each do |item|
|
135
|
-
if
|
135
|
+
if item.get('itemattributes/title') =~ /#{criterion}/i
|
136
136
|
products << item
|
137
137
|
end
|
138
138
|
end
|
@@ -140,14 +140,14 @@ module Alexandria
|
|
140
140
|
|
141
141
|
when SEARCH_BY_AUTHORS
|
142
142
|
criterion = "author:#{criterion}"
|
143
|
-
res = Amazon::Ecs.item_search(criterion,
|
143
|
+
res = Amazon::Ecs.item_search(criterion, response_group: 'ItemAttributes,Images', country: request_locale, type: 'Power')
|
144
144
|
res.items.each do |item|
|
145
145
|
products << item
|
146
146
|
end
|
147
147
|
# #req.author_search(criterion) do |product|
|
148
148
|
|
149
149
|
when SEARCH_BY_KEYWORD
|
150
|
-
res = Amazon::Ecs.item_search(criterion,
|
150
|
+
res = Amazon::Ecs.item_search(criterion, response_group: 'ItemAttributes,Images', country: request_locale)
|
151
151
|
|
152
152
|
res.items.each do |item|
|
153
153
|
products << item
|
@@ -172,25 +172,13 @@ module Alexandria
|
|
172
172
|
atts = item.search_and_convert('itemattributes')
|
173
173
|
title = normalize(atts.get('title'))
|
174
174
|
|
175
|
-
# Work around Amazon US encoding bug. Amazon US apparently
|
176
|
-
# interprets UTF-8 titles as ISO-8859 titles and then converts
|
177
|
-
# the garbled titles to UTF-8. This tries to convert back into
|
178
|
-
# valid UTF-8. It does not always work - see isbn 2259196098
|
179
|
-
# (from the mailing list) for an example.
|
180
|
-
# if req.locale == 'us'
|
181
|
-
# title = title.convert('ISO-8859-1','UTF-8')
|
182
|
-
# end
|
183
|
-
# Cathal Mc Ginley 2008-02-18, still a problem for that ISBN!! Yep 2009-12-09!
|
184
|
-
|
185
175
|
media = normalize(atts.get('binding'))
|
186
176
|
media = nil if media == 'Unknown Binding'
|
187
177
|
|
188
178
|
isbn = normalize(atts.get('isbn'))
|
189
|
-
if isbn
|
190
|
-
|
191
|
-
|
192
|
-
isbn = nil # it may be an ASIN which is not an ISBN
|
193
|
-
end
|
179
|
+
isbn = if isbn && Library.valid_isbn?(isbn)
|
180
|
+
Library.canonicalise_ean(isbn)
|
181
|
+
end
|
194
182
|
# hack, extract year by regexp (not Y10K compatible :-)
|
195
183
|
/([1-9][0-9]{3})/ =~ atts.get('publicationdate')
|
196
184
|
publishing_year = Regexp.last_match[1] ? Regexp.last_match[1].to_i : nil
|
@@ -251,9 +239,7 @@ module Alexandria
|
|
251
239
|
end
|
252
240
|
|
253
241
|
def normalize(str)
|
254
|
-
unless str.nil?
|
255
|
-
str = str.squeeze(' ').strip
|
256
|
-
end
|
242
|
+
str = str.squeeze(' ').strip unless str.nil?
|
257
243
|
str
|
258
244
|
end
|
259
245
|
end
|
@@ -45,8 +45,7 @@ module Amazon
|
|
45
45
|
ca: 'http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService',
|
46
46
|
de: 'http://webservices.amazon.de/onca/xml?Service=AWSECommerceService',
|
47
47
|
jp: 'http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService',
|
48
|
-
fr: 'http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService'
|
49
|
-
}
|
48
|
+
fr: 'http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService' }.freeze
|
50
49
|
|
51
50
|
@@options = {}
|
52
51
|
@@debug = false
|
@@ -202,12 +201,12 @@ module Amazon
|
|
202
201
|
|
203
202
|
def self.prepare_url(opts)
|
204
203
|
country = opts.delete(:country)
|
205
|
-
country =
|
204
|
+
country = country.nil? ? 'us' : country
|
206
205
|
request_url = SERVICE_URLS[country.to_sym]
|
207
206
|
raise Amazon::RequestError, "Invalid country '#{country}'" unless request_url
|
208
207
|
|
209
208
|
qs = ''
|
210
|
-
opts.each {|k, v|
|
209
|
+
opts.each { |k, v|
|
211
210
|
next unless v
|
212
211
|
v = v.join(',') if v.is_a? Array
|
213
212
|
qs << "&#{camelize(k.to_s)}=#{URI.encode(v.to_s)}"
|
@@ -317,7 +316,7 @@ module Amazon
|
|
317
316
|
# Find Hpricot::Elements matching the given path. Example: element/"author".
|
318
317
|
def /(path)
|
319
318
|
elements = @element / path
|
320
|
-
return nil if elements.
|
319
|
+
return nil if elements.empty?
|
321
320
|
elements
|
322
321
|
end
|
323
322
|
|
@@ -374,7 +373,7 @@ module Amazon
|
|
374
373
|
result = element / path
|
375
374
|
if (result.is_a? Hpricot::Elements) || (result.is_a? Array)
|
376
375
|
parsed_result = []
|
377
|
-
result.each {|item|
|
376
|
+
result.each { |item|
|
378
377
|
parsed_result << Element.get(item)
|
379
378
|
}
|
380
379
|
parsed_result
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
#
|
3
3
|
# Copyright (C) 2009 Cathal Mc Ginley
|
4
|
-
# Copyright (C) 2011, 2014 Matijs van Zuijlen
|
4
|
+
# Copyright (C) 2011, 2014, 2015 Matijs van Zuijlen
|
5
5
|
#
|
6
6
|
# Alexandria is free software; you can redistribute it and/or
|
7
7
|
# modify it under the terms of the GNU General Public License as
|
@@ -35,13 +35,12 @@ module Alexandria
|
|
35
35
|
class BarnesAndNobleProvider < WebsiteBasedProvider
|
36
36
|
include Alexandria::Logging
|
37
37
|
|
38
|
-
SITE = 'http://www.barnesandnoble.com'
|
38
|
+
SITE = 'http://www.barnesandnoble.com'.freeze
|
39
39
|
|
40
|
-
BASE_ISBN_SEARCH_URL = 'http://
|
41
|
-
'/product.aspx?ISBSRC=Y&ISBN=%s'
|
40
|
+
BASE_ISBN_SEARCH_URL = 'http://www.barnesandnoble.com/s/%s'.freeze
|
42
41
|
|
43
42
|
BASE_SEARCH_URL = 'http://search.barnesandnoble.com/booksearch' \
|
44
|
-
'/results.asp?%s=%s' # type, term
|
43
|
+
'/results.asp?%s=%s'.freeze # type, term
|
45
44
|
|
46
45
|
def initialize
|
47
46
|
super('BarnesAndNoble', 'BarnesAndNoble')
|
@@ -50,25 +49,28 @@ module Alexandria
|
|
50
49
|
end
|
51
50
|
|
52
51
|
def agent
|
53
|
-
unless @agent
|
54
|
-
@agent = Alexandria::WWWAgent.new
|
55
|
-
end
|
52
|
+
@agent = Alexandria::WWWAgent.new unless @agent
|
56
53
|
@agent
|
57
54
|
end
|
58
55
|
|
59
56
|
def fetch_redirectly(uri_str, limit = 5)
|
60
|
-
raise NoResultsError, 'HTTP redirect too deep' if limit
|
61
|
-
response = agent.get(uri_str)
|
57
|
+
raise NoResultsError, 'HTTP redirect too deep' if limit.zero?
|
62
58
|
if limit < 10
|
63
59
|
sleep 0.1
|
64
|
-
|
60
|
+
log.debug { "Redirectly :: #{uri_str}" }
|
65
61
|
else
|
66
|
-
|
62
|
+
log.debug { "Fetching :: #{uri_str}" }
|
67
63
|
end
|
68
|
-
|
64
|
+
response = agent.get(uri_str)
|
65
|
+
log.debug { response.inspect }
|
69
66
|
case response
|
70
67
|
when Net::HTTPSuccess then response
|
71
|
-
when Net::HTTPRedirection then
|
68
|
+
when Net::HTTPRedirection then
|
69
|
+
redirect = URI.parse response['Location']
|
70
|
+
if redirect.relative?
|
71
|
+
redirect = URI.parse(uri_str) + redirect
|
72
|
+
end
|
73
|
+
fetch_redirectly(redirect.to_s, (limit - 1))
|
72
74
|
else
|
73
75
|
response.error!
|
74
76
|
end
|
@@ -97,11 +99,11 @@ module Alexandria
|
|
97
99
|
end
|
98
100
|
|
99
101
|
def create_search_uri(search_type, search_term)
|
100
|
-
search_type_code = {
|
102
|
+
(search_type_code = {
|
101
103
|
SEARCH_BY_AUTHORS => 'ATH',
|
102
104
|
SEARCH_BY_TITLE => 'TTL',
|
103
|
-
SEARCH_BY_KEYWORD => 'WRD'
|
104
|
-
}[search_type]
|
105
|
+
SEARCH_BY_KEYWORD => 'WRD' # SEARCH_BY_PUBLISHER => 'PBL' # not implemented
|
106
|
+
}[search_type]) || ''
|
105
107
|
if search_type == SEARCH_BY_ISBN
|
106
108
|
BASE_ISBN_SEARCH_URL % Library.canonicalise_ean(search_term) # isbn-13
|
107
109
|
else
|
@@ -112,7 +114,7 @@ module Alexandria
|
|
112
114
|
|
113
115
|
def get_book_from_search_result(result)
|
114
116
|
log.debug { "Fetching book from #{result[:url]}" }
|
115
|
-
html_data =
|
117
|
+
html_data = transport.get_response(URI.parse(result[:url]))
|
116
118
|
parse_result_data(html_data.body)
|
117
119
|
end
|
118
120
|
|
@@ -146,71 +148,44 @@ module Alexandria
|
|
146
148
|
doc = html_to_doc(html)
|
147
149
|
begin
|
148
150
|
book_data = {}
|
149
|
-
title_header = doc % '//div.wgt-productTitle/h1'
|
150
|
-
if title_header
|
151
|
-
title = ''
|
152
|
-
title_header.children.each do |node|
|
153
|
-
if node.text?
|
154
|
-
title += ' ' + node.to_s
|
155
|
-
end
|
156
|
-
end
|
157
|
-
title.strip!
|
158
|
-
if title.empty?
|
159
|
-
log.warn { 'Unexpectedly found no title in BarnesAndNoble lookup' }
|
160
|
-
raise NoResultsError
|
161
|
-
end
|
162
|
-
book_data[:title] = title.strip.squeeze(' ')
|
163
|
-
subtitle_span = title_header % 'span.subtitle'
|
164
|
-
if subtitle_span
|
165
|
-
book_data[:title] += " #{subtitle_span.inner_text}"
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
isbn_links = doc / '//a.isbn-a'
|
170
|
-
isbns = isbn_links.map(&:inner_text)
|
171
|
-
book_data[:isbn] = Library.canonicalise_ean(isbns.first)
|
172
151
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
152
|
+
dl = (doc / 'dl').first
|
153
|
+
dts = dl.children_of_type('dt')
|
154
|
+
dts.each do |dt|
|
155
|
+
value = dt.next_sibling.inner_text
|
156
|
+
case dt.inner_text
|
157
|
+
when /ISBN-13/
|
158
|
+
book_data[:isbn] = Library.canonicalise_ean(value)
|
159
|
+
when /Publisher/
|
160
|
+
book_data[:publisher] = value
|
161
|
+
when /Publication data/
|
162
|
+
value =~ /\d{2}.\d{2}.(\d{4})/
|
163
|
+
year = Regexp.last_match[1]
|
164
|
+
book_data[:publisher] = year
|
165
|
+
end
|
177
166
|
end
|
178
|
-
book_data[:authors] = authors
|
179
167
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
168
|
+
meta = doc / 'meta'
|
169
|
+
meta.each do |it|
|
170
|
+
attrs = it.attributes
|
171
|
+
property = attrs['property']
|
172
|
+
next unless property
|
173
|
+
case property
|
174
|
+
when 'og:title'
|
175
|
+
book_data[:title] = attrs['content']
|
176
|
+
when 'og:image'
|
177
|
+
book_data[:image_url] = attrs['content']
|
178
|
+
end
|
184
179
|
end
|
185
180
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
year = Regexp.last_match[2].to_i if Regexp.last_match[2]
|
190
|
-
book_data[:publication_year] = year
|
191
|
-
end
|
181
|
+
author_links = doc / 'span.contributors a'
|
182
|
+
authors = author_links.map(&:inner_text)
|
183
|
+
book_data[:authors] = authors
|
192
184
|
|
193
185
|
book_data[:binding] = ''
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
book_data[:binding] = Regexp.last_match[1]
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
product_image_div = doc % 'div#product-image'
|
202
|
-
if product_image_div
|
203
|
-
images = product_image_div / 'img'
|
204
|
-
if images.size == 1
|
205
|
-
book_data[:image_url] = images.first['src']
|
206
|
-
else
|
207
|
-
if images.first['src'] =~ /see_inside.gif/
|
208
|
-
# the first image is the "See Inside!" label
|
209
|
-
book_data[:image_url] = images[1]['src']
|
210
|
-
else
|
211
|
-
book_data[:image_url] = images.first['src']
|
212
|
-
end
|
213
|
-
end
|
186
|
+
selected_format = (doc / '#availableFormats li.selected a.tabTitle').first
|
187
|
+
if selected_format
|
188
|
+
book_data[:binding] = selected_format.inner_text
|
214
189
|
end
|
215
190
|
|
216
191
|
book = Book.new(book_data[:title], book_data[:authors],
|