alexandria-book-collection-manager 0.6.9.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rubocop.yml +68 -0
  4. data/.rubocop_todo.yml +200 -0
  5. data/CHANGELOG.md +23 -0
  6. data/COPYING +339 -0
  7. data/ChangeLog.0 +3598 -0
  8. data/Gemfile +9 -0
  9. data/INSTALL.rdoc +156 -0
  10. data/PACKAGING +36 -0
  11. data/README.md +88 -0
  12. data/RELEASE_CHECKLIST +18 -0
  13. data/Rakefile +264 -0
  14. data/TODO +24 -0
  15. data/alexandria-book-collection-manager.gemspec +35 -0
  16. data/alexandria.desktop.in +10 -0
  17. data/bin/alexandria +82 -0
  18. data/doc/AUTHORS +60 -0
  19. data/doc/BUGS +31 -0
  20. data/doc/FAQ +369 -0
  21. data/doc/HACKING +19 -0
  22. data/doc/NEWS +341 -0
  23. data/doc/alexandria.1 +120 -0
  24. data/doc/cuecat_support.rdoc +67 -0
  25. data/lib/alexandria.rb +85 -0
  26. data/lib/alexandria/about.rb +82 -0
  27. data/lib/alexandria/book_providers.rb +411 -0
  28. data/lib/alexandria/book_providers/adlibris.rb +235 -0
  29. data/lib/alexandria/book_providers/amazon_aws.rb +261 -0
  30. data/lib/alexandria/book_providers/amazon_ecs_util.rb +405 -0
  31. data/lib/alexandria/book_providers/barnes_and_noble.rb +229 -0
  32. data/lib/alexandria/book_providers/bol_it.rb +162 -0
  33. data/lib/alexandria/book_providers/deastore.rb +277 -0
  34. data/lib/alexandria/book_providers/douban.rb +135 -0
  35. data/lib/alexandria/book_providers/ibs_it.rb +149 -0
  36. data/lib/alexandria/book_providers/mcu.rb +177 -0
  37. data/lib/alexandria/book_providers/proxis.rb +205 -0
  38. data/lib/alexandria/book_providers/pseudomarc.rb +185 -0
  39. data/lib/alexandria/book_providers/renaud.rb +142 -0
  40. data/lib/alexandria/book_providers/siciliano.rb +271 -0
  41. data/lib/alexandria/book_providers/thalia.rb +197 -0
  42. data/lib/alexandria/book_providers/web.rb +59 -0
  43. data/lib/alexandria/book_providers/webster_it.rb +173 -0
  44. data/lib/alexandria/book_providers/worldcat.rb +251 -0
  45. data/lib/alexandria/book_providers/z3950.rb +422 -0
  46. data/lib/alexandria/config.rb +8 -0
  47. data/lib/alexandria/console.rb +31 -0
  48. data/lib/alexandria/execution_queue.rb +96 -0
  49. data/lib/alexandria/export_library.rb +536 -0
  50. data/lib/alexandria/import_library.rb +316 -0
  51. data/lib/alexandria/import_library_csv.rb +270 -0
  52. data/lib/alexandria/logging.rb +159 -0
  53. data/lib/alexandria/models/book.rb +72 -0
  54. data/lib/alexandria/models/library.rb +714 -0
  55. data/lib/alexandria/net.rb +53 -0
  56. data/lib/alexandria/preferences.rb +324 -0
  57. data/lib/alexandria/scanners.rb +42 -0
  58. data/lib/alexandria/scanners/cuecat.rb +118 -0
  59. data/lib/alexandria/scanners/keyboard.rb +57 -0
  60. data/lib/alexandria/smart_library.rb +525 -0
  61. data/lib/alexandria/ui.rb +53 -0
  62. data/lib/alexandria/ui/builder_base.rb +43 -0
  63. data/lib/alexandria/ui/callbacks.rb +389 -0
  64. data/lib/alexandria/ui/completion_models.rb +228 -0
  65. data/lib/alexandria/ui/dialogs/about_dialog.rb +59 -0
  66. data/lib/alexandria/ui/dialogs/acquire_dialog.rb +640 -0
  67. data/lib/alexandria/ui/dialogs/alert_dialog.rb +68 -0
  68. data/lib/alexandria/ui/dialogs/bad_isbns_dialog.rb +43 -0
  69. data/lib/alexandria/ui/dialogs/barcode_animation.rb +159 -0
  70. data/lib/alexandria/ui/dialogs/book_properties_dialog.rb +210 -0
  71. data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +432 -0
  72. data/lib/alexandria/ui/dialogs/export_dialog.rb +172 -0
  73. data/lib/alexandria/ui/dialogs/import_dialog.rb +205 -0
  74. data/lib/alexandria/ui/dialogs/misc_dialogs.rb +85 -0
  75. data/lib/alexandria/ui/dialogs/new_book_dialog.rb +639 -0
  76. data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +153 -0
  77. data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +67 -0
  78. data/lib/alexandria/ui/dialogs/preferences_dialog.rb +587 -0
  79. data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +56 -0
  80. data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +432 -0
  81. data/lib/alexandria/ui/dndable.rb +81 -0
  82. data/lib/alexandria/ui/gtk_thread_help.rb +88 -0
  83. data/lib/alexandria/ui/icons.rb +100 -0
  84. data/lib/alexandria/ui/iconview.rb +91 -0
  85. data/lib/alexandria/ui/iconview_tooltips.rb +162 -0
  86. data/lib/alexandria/ui/init.rb +93 -0
  87. data/lib/alexandria/ui/libraries_combo.rb +75 -0
  88. data/lib/alexandria/ui/listview.rb +310 -0
  89. data/lib/alexandria/ui/main_app.rb +67 -0
  90. data/lib/alexandria/ui/multi_drag_treeview.rb +150 -0
  91. data/lib/alexandria/ui/sidepane.rb +194 -0
  92. data/lib/alexandria/ui/sound.rb +107 -0
  93. data/lib/alexandria/ui/ui_manager.rb +1308 -0
  94. data/lib/alexandria/undo_manager.rb +78 -0
  95. data/lib/alexandria/utils.rb +30 -0
  96. data/lib/alexandria/version.rb +24 -0
  97. data/lib/alexandria/web_themes.rb +75 -0
  98. data/misc/sounds/README +15 -0
  99. data/misc/sounds/bad_scan.csd +62 -0
  100. data/misc/sounds/good_scan.csd +61 -0
  101. data/misc/sounds/scanning.csd +46 -0
  102. data/po/ChangeLog +488 -0
  103. data/po/Makefile +44 -0
  104. data/po/README +29 -0
  105. data/po/commit-po +72 -0
  106. data/po/cs.po +1437 -0
  107. data/po/cy.po +1521 -0
  108. data/po/de.po +1400 -0
  109. data/po/el.po +1379 -0
  110. data/po/es.po +1376 -0
  111. data/po/fr.po +1420 -0
  112. data/po/ga.po +1359 -0
  113. data/po/gl.po +1397 -0
  114. data/po/it.po +1406 -0
  115. data/po/ja.po +1355 -0
  116. data/po/mk.po +1373 -0
  117. data/po/nb.po +1386 -0
  118. data/po/nl.po +1405 -0
  119. data/po/pl.po +1373 -0
  120. data/po/pt.po +1398 -0
  121. data/po/pt_BR.po +1409 -0
  122. data/po/ru.po +1372 -0
  123. data/po/sk.po +1380 -0
  124. data/po/sv.po +1402 -0
  125. data/po/uk.po +1423 -0
  126. data/po/zh_TW.po +1394 -0
  127. data/schemas/alexandria.schemas +300 -0
  128. data/share/alexandria/glade/acquire_dialog__builder.glade +201 -0
  129. data/share/alexandria/glade/book_properties_dialog__builder.glade +910 -0
  130. data/share/alexandria/glade/main_app__builder.glade +229 -0
  131. data/share/alexandria/glade/new_book_dialog__builder.glade +379 -0
  132. data/share/alexandria/glade/preferences_dialog__builder.glade +733 -0
  133. data/share/alexandria/icons/alexandria.png +0 -0
  134. data/share/alexandria/icons/alexandria_small.png +0 -0
  135. data/share/alexandria/icons/book.png +0 -0
  136. data/share/alexandria/icons/book_icon.png +0 -0
  137. data/share/alexandria/icons/book_small.png +0 -0
  138. data/share/alexandria/icons/cuecat.png +0 -0
  139. data/share/alexandria/icons/cuecat_inactive.png +0 -0
  140. data/share/alexandria/icons/favorite_tag.png +0 -0
  141. data/share/alexandria/icons/less.png +0 -0
  142. data/share/alexandria/icons/library.png +0 -0
  143. data/share/alexandria/icons/library_small.png +0 -0
  144. data/share/alexandria/icons/lookup.png +0 -0
  145. data/share/alexandria/icons/more.png +0 -0
  146. data/share/alexandria/icons/no_cover.png +0 -0
  147. data/share/alexandria/icons/smart_library.png +0 -0
  148. data/share/alexandria/icons/smart_library_small.png +0 -0
  149. data/share/alexandria/icons/star_set.png +0 -0
  150. data/share/alexandria/icons/star_unset.png +0 -0
  151. data/share/alexandria/icons/view_as_icons.png +0 -0
  152. data/share/alexandria/icons/view_as_list.png +0 -0
  153. data/share/alexandria/ui/menus.xml +91 -0
  154. data/share/alexandria/ui/popups.xml +91 -0
  155. data/share/alexandria/web-themes/clean/clean.css +85 -0
  156. data/share/alexandria/web-themes/clean/preview.jpg +0 -0
  157. data/share/alexandria/web-themes/list/list.css +105 -0
  158. data/share/alexandria/web-themes/list/preview.jpg +0 -0
  159. data/share/app-icon/16x16/alexandria.png +0 -0
  160. data/share/app-icon/16x16/alexandria.svg +263 -0
  161. data/share/app-icon/22x22/alexandria.png +0 -0
  162. data/share/app-icon/22x22/alexandria.svg +465 -0
  163. data/share/app-icon/24x24/alexandria.png +0 -0
  164. data/share/app-icon/32x32/alexandria.png +0 -0
  165. data/share/app-icon/32x32/alexandria.svg +813 -0
  166. data/share/app-icon/32x32/alexandria.xpm +241 -0
  167. data/share/app-icon/48x48/alexandria.png +0 -0
  168. data/share/app-icon/scalable/alexandria.svg +700 -0
  169. data/share/gnome/help/alexandria/C/about.xml +44 -0
  170. data/share/gnome/help/alexandria/C/adding-books.xml +339 -0
  171. data/share/gnome/help/alexandria/C/alexandria.xml +185 -0
  172. data/share/gnome/help/alexandria/C/bugs.xml +18 -0
  173. data/share/gnome/help/alexandria/C/editing-book-properties.xml +124 -0
  174. data/share/gnome/help/alexandria/C/exporting.xml +81 -0
  175. data/share/gnome/help/alexandria/C/figures/adding_books_acquire_from_scanner_process.png +0 -0
  176. data/share/gnome/help/alexandria/C/figures/adding_books_add_by_isbn.png +0 -0
  177. data/share/gnome/help/alexandria/C/figures/adding_books_isbn_import.png +0 -0
  178. data/share/gnome/help/alexandria/C/figures/adding_books_manual_details.png +0 -0
  179. data/share/gnome/help/alexandria/C/figures/adding_books_rename_library_after_import.png +0 -0
  180. data/share/gnome/help/alexandria/C/figures/adding_books_search_results.png +0 -0
  181. data/share/gnome/help/alexandria/C/figures/editing_book_properties_info.png +0 -0
  182. data/share/gnome/help/alexandria/C/figures/editing_book_properties_loaning.png +0 -0
  183. data/share/gnome/help/alexandria/C/figures/exporting_information_html.png +0 -0
  184. data/share/gnome/help/alexandria/C/figures/getting_started_first_launched.png +0 -0
  185. data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view.png +0 -0
  186. data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view_search.png +0 -0
  187. data/share/gnome/help/alexandria/C/figures/settings_providers_new_z3950.png +0 -0
  188. data/share/gnome/help/alexandria/C/figures/smart_libraries_new_smart_library.png +0 -0
  189. data/share/gnome/help/alexandria/C/figures/working_with_libraries_library_pane.png +0 -0
  190. data/share/gnome/help/alexandria/C/getting-started.xml +154 -0
  191. data/share/gnome/help/alexandria/C/gnu-fdl-1.2.xml +543 -0
  192. data/share/gnome/help/alexandria/C/introduction.xml +142 -0
  193. data/share/gnome/help/alexandria/C/searching.xml +90 -0
  194. data/share/gnome/help/alexandria/C/settings.xml +140 -0
  195. data/share/gnome/help/alexandria/C/smart-libraries.xml +160 -0
  196. data/share/gnome/help/alexandria/C/working-with-libraries.xml +76 -0
  197. data/share/gnome/help/alexandria/ChangeLog +99 -0
  198. data/share/gnome/help/alexandria/fr/alexandria.xml +2292 -0
  199. data/share/gnome/help/alexandria/fr/figures/alexandria_add_button.png +0 -0
  200. data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_isbn_1.png +0 -0
  201. data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_search_1.png +0 -0
  202. data/share/gnome/help/alexandria/fr/figures/alexandria_add_manually.png +0 -0
  203. data/share/gnome/help/alexandria/fr/figures/alexandria_add_z3950.png +0 -0
  204. data/share/gnome/help/alexandria/fr/figures/alexandria_close_button.png +0 -0
  205. data/share/gnome/help/alexandria/fr/figures/alexandria_edit_info.png +0 -0
  206. data/share/gnome/help/alexandria/fr/figures/alexandria_export_web_page.png +0 -0
  207. data/share/gnome/help/alexandria/fr/figures/alexandria_importing.png +0 -0
  208. data/share/gnome/help/alexandria/fr/figures/alexandria_library_pane.png +0 -0
  209. data/share/gnome/help/alexandria/fr/figures/alexandria_list_view.png +0 -0
  210. data/share/gnome/help/alexandria/fr/figures/alexandria_list_view_search.png +0 -0
  211. data/share/gnome/help/alexandria/fr/figures/alexandria_loaning.png +0 -0
  212. data/share/gnome/help/alexandria/fr/figures/alexandria_main_window.png +0 -0
  213. data/share/gnome/help/alexandria/fr/figures/alexandria_remove_button.png +0 -0
  214. data/share/gnome/help/alexandria/ja/about.xml +33 -0
  215. data/share/gnome/help/alexandria/ja/adding-books.xml +314 -0
  216. data/share/gnome/help/alexandria/ja/alexandria.xml +172 -0
  217. data/share/gnome/help/alexandria/ja/bugs.xml +11 -0
  218. data/share/gnome/help/alexandria/ja/editing-book-properties.xml +100 -0
  219. data/share/gnome/help/alexandria/ja/exporting.xml +98 -0
  220. data/share/gnome/help/alexandria/ja/figures/adding_books_acquire_from_scanner_process.png +0 -0
  221. data/share/gnome/help/alexandria/ja/figures/adding_books_add_by_isbn.png +0 -0
  222. data/share/gnome/help/alexandria/ja/figures/adding_books_isbn_import.png +0 -0
  223. data/share/gnome/help/alexandria/ja/figures/adding_books_manual_details.png +0 -0
  224. data/share/gnome/help/alexandria/ja/figures/adding_books_rename_library_after_import.png +0 -0
  225. data/share/gnome/help/alexandria/ja/figures/adding_books_search_results.png +0 -0
  226. data/share/gnome/help/alexandria/ja/figures/editing_book_properties_info.png +0 -0
  227. data/share/gnome/help/alexandria/ja/figures/editing_book_properties_loaning.png +0 -0
  228. data/share/gnome/help/alexandria/ja/figures/exporting_information_html.png +0 -0
  229. data/share/gnome/help/alexandria/ja/figures/getting_started_first_launched.png +0 -0
  230. data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view.png +0 -0
  231. data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view_search.png +0 -0
  232. data/share/gnome/help/alexandria/ja/figures/settings_providers_new_z3950.png +0 -0
  233. data/share/gnome/help/alexandria/ja/figures/smart_libraries_new_smart_library.png +0 -0
  234. data/share/gnome/help/alexandria/ja/figures/working_with_libraries_library_pane.png +0 -0
  235. data/share/gnome/help/alexandria/ja/getting-started.xml +144 -0
  236. data/share/gnome/help/alexandria/ja/gnu-fdl-1.2.xml +541 -0
  237. data/share/gnome/help/alexandria/ja/introduction.xml +134 -0
  238. data/share/gnome/help/alexandria/ja/searching.xml +104 -0
  239. data/share/gnome/help/alexandria/ja/settings.xml +129 -0
  240. data/share/gnome/help/alexandria/ja/smart-libraries.xml +140 -0
  241. data/share/gnome/help/alexandria/ja/working-with-libraries.xml +88 -0
  242. data/share/menu/alexandria +7 -0
  243. data/share/omf/alexandria/alexandria-C.omf.in +27 -0
  244. data/share/omf/alexandria/alexandria-fr.omf.in +30 -0
  245. data/share/sounds/alexandria/bad_scan.ogg +0 -0
  246. data/share/sounds/alexandria/bad_scan.wav +0 -0
  247. data/share/sounds/alexandria/good_scan.ogg +0 -0
  248. data/share/sounds/alexandria/good_scan.wav +0 -0
  249. data/share/sounds/alexandria/scanning.ogg +0 -0
  250. data/share/sounds/alexandria/scanning.wav +0 -0
  251. data/spec/alexandria/library_spec.rb +205 -0
  252. data/spec/alexandria/preferences_spec.rb +22 -0
  253. data/spec/alexandria/scanners/cuecat_spec.rb +68 -0
  254. data/spec/alexandria/smart_library_spec.rb +22 -0
  255. data/spec/alexandria/ui/dialogs_spec.rb +90 -0
  256. data/spec/alexandria/ui/iconview_spec.rb +27 -0
  257. data/spec/alexandria/ui/listview_spec.rb +28 -0
  258. data/spec/alexandria/ui/main_app_spec.rb +48 -0
  259. data/spec/alexandria/ui/sidepane_spec.rb +27 -0
  260. data/spec/alexandria/ui/ui_manager_spec.rb +26 -0
  261. data/spec/alexandria/ui/ui_utilities_spec.rb +60 -0
  262. data/spec/alexandria/utilities_spec.rb +50 -0
  263. data/spec/data/libraries/0.6.1-noisbn/My Library/0201398257.yaml +10 -0
  264. data/spec/data/libraries/0.6.1-noisbn/My Library/1565920007.yaml +10 -0
  265. data/spec/data/libraries/0.6.1/My Library/0192812173.yaml +9 -0
  266. data/spec/data/libraries/0.6.1/My Library/0201398257.cover +0 -0
  267. data/spec/data/libraries/0.6.1/My Library/0201398257.yaml +10 -0
  268. data/spec/data/libraries/0.6.1/My Library/1565920007.yaml +10 -0
  269. data/spec/data/libraries/0.6.2/My Library/9780140266146.cover +0 -0
  270. data/spec/data/libraries/0.6.2/My Library/9780140266146.yaml +16 -0
  271. data/spec/data/libraries/0.6.2/My Library/9780140278781.cover +0 -0
  272. data/spec/data/libraries/0.6.2/My Library/9780140278781.yaml +21 -0
  273. data/spec/data/libraries/0.6.2/My Library/9780571147168.cover +0 -0
  274. data/spec/data/libraries/0.6.2/My Library/9780571147168.yaml +20 -0
  275. data/spec/data/libraries/0.6.2/My Library/9780575079038.cover +0 -0
  276. data/spec/data/libraries/0.6.2/My Library/9780575079038.yaml +20 -0
  277. data/spec/data/libraries/0.6.2/My Library/9780755322800.cover +0 -0
  278. data/spec/data/libraries/0.6.2/My Library/9780755322800.yaml +20 -0
  279. data/spec/spec_helper.rb +46 -0
  280. data/tasks/rdoc.rake +6 -0
  281. data/tasks/setup.rb +30 -0
  282. data/tasks/spec.rake +29 -0
  283. data/tasks/test.rake +38 -0
  284. data/test/application_test.rb +39 -0
  285. data/test/book_test.rb +34 -0
  286. data/test/data/isbns.txt +3 -0
  287. data/test/isbn_test.rb +68 -0
  288. data/test/providers_test.rb +254 -0
  289. data/test/test_helper.rb +42 -0
  290. data/util/rake/fileinstall.rb +313 -0
  291. data/util/rake/gettextgenerate.rb +158 -0
  292. data/util/rake/omfgenerate.rb +79 -0
  293. metadata +452 -0
@@ -0,0 +1,405 @@
1
+ #--
2
+ # Copyright (c) 2006 Herryanto Siatono, Pluit Solutions
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ # Modified by Cathal Mc Ginley 2008-02-18
25
+ # added Amazon::Ecs.transport - to enable Alexandria's proxy support
26
+ # Modified by Cathal Mc Ginley 2008-08-26
27
+ # Amazon::Element.get now uses inner_text, not inner_html, fixing #21659
28
+ # Modified by Cathal Mc Ginley 2009-08-13
29
+ # Added sign_request and hmac_sha256 methods for Authentication support
30
+
31
+ require 'net/http'
32
+ require 'hpricot'
33
+ require 'cgi'
34
+
35
+ require 'digest/sha2'
36
+
37
+ module Amazon
38
+ class RequestError < StandardError; end
39
+
40
+ class Ecs
41
+ include Alexandria::Logging
42
+
43
+ SERVICE_URLS = { us: 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService',
44
+ uk: 'http://webservices.amazon.co.uk/onca/xml?Service=AWSECommerceService',
45
+ ca: 'http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService',
46
+ de: 'http://webservices.amazon.de/onca/xml?Service=AWSECommerceService',
47
+ jp: 'http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService',
48
+ fr: 'http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService'
49
+ }
50
+
51
+ @@options = {}
52
+ @@debug = false
53
+
54
+ @@secret_access_key = ''
55
+
56
+ # Default search options
57
+ def self.options
58
+ @@options
59
+ end
60
+
61
+ def self.secret_access_key=(key)
62
+ @@secret_access_key = key
63
+ end
64
+
65
+ # Set default search options
66
+ def self.options=(opts)
67
+ @@options = opts
68
+ end
69
+
70
+ # Get debug flag.
71
+ def self.debug
72
+ @@debug
73
+ end
74
+
75
+ # Set debug flag to true or false.
76
+ def self.debug=(dbg)
77
+ @@debug = dbg
78
+ end
79
+
80
+ def self.configure(&_proc)
81
+ raise ArgumentError, 'Block is required.' unless block_given?
82
+ yield @@options
83
+ end
84
+
85
+ # Search amazon items with search terms. Default search index option is 'Books'.
86
+ # For other search type other than keywords, please specify :type => [search type param name].
87
+ def self.item_search(terms, opts = {})
88
+ opts[:operation] = 'ItemSearch'
89
+ opts[:search_index] = opts[:search_index] || 'Books'
90
+
91
+ type = opts.delete(:type)
92
+ if type
93
+ opts[type.to_sym] = terms
94
+ else
95
+ opts[:keywords] = terms
96
+ end
97
+
98
+ send_request(opts)
99
+ end
100
+
101
+ # Search an item by ASIN no.
102
+ def self.item_lookup(item_id, opts = {})
103
+ opts[:operation] = 'ItemLookup'
104
+ opts[:item_id] = item_id
105
+
106
+ send_request(opts)
107
+ end
108
+
109
+ # HACK : copied from book_providers.rb
110
+ def self.transport
111
+ config = Alexandria::Preferences.instance.http_proxy_config
112
+ config ? Net::HTTP.Proxy(*config) : Net::HTTP
113
+ end
114
+
115
+ # Generic send request to ECS REST service. You have to specify the :operation parameter.
116
+ def self.send_request(opts)
117
+ opts = options.merge(opts) if options
118
+ request_url = prepare_url(opts)
119
+ log.debug { "Request URL: #{request_url}" }
120
+
121
+ res = transport.get_response(URI.parse(request_url))
122
+ unless res.is_a? Net::HTTPSuccess
123
+ raise Amazon::RequestError, "HTTP Response: #{res.code} #{res.message}"
124
+ end
125
+ Response.new(res.body)
126
+ end
127
+
128
+ # Response object returned after a REST call to Amazon service.
129
+ class Response
130
+ # XML input is in string format
131
+ def initialize(xml)
132
+ @doc = Hpricot(xml)
133
+ end
134
+
135
+ # Return Hpricot object.
136
+ attr_reader :doc
137
+
138
+ # Return true if request is valid.
139
+ def is_valid_request?
140
+ (@doc / 'isvalid').inner_html == 'True'
141
+ end
142
+
143
+ # Return true if response has an error.
144
+ def has_error?
145
+ !(error.nil? || error.empty?)
146
+ end
147
+
148
+ # Return error message.
149
+ def error
150
+ Element.get(@doc, 'error/message')
151
+ end
152
+
153
+ # Return an array of Amazon::Element item objects.
154
+ def items
155
+ unless @items
156
+ @items = (@doc / 'item').map { |item| Element.new(item) }
157
+ end
158
+ @items
159
+ end
160
+
161
+ # Return the first item (Amazon::Element)
162
+ def first_item
163
+ items.first
164
+ end
165
+
166
+ # Return current page no if :item_page option is when initiating the request.
167
+ def item_page
168
+ unless @item_page
169
+ @item_page = (@doc / 'itemsearchrequest/itempage').inner_html.to_i
170
+ end
171
+ @item_page
172
+ end
173
+
174
+ # Return total results.
175
+ def total_results
176
+ unless @total_results
177
+ @total_results = (@doc / 'totalresults').inner_html.to_i
178
+ end
179
+ @total_results
180
+ end
181
+
182
+ # Return total pages.
183
+ def total_pages
184
+ unless @total_pages
185
+ @total_pages = (@doc / 'totalpages').inner_html.to_i
186
+ end
187
+ @total_pages
188
+ end
189
+ end
190
+
191
+ # protected
192
+ # def self.log(s)
193
+ # return unless self.debug
194
+ # if defined? RAILS_DEFAULT_LOGGER
195
+ # RAILS_DEFAULT_LOGGER.error(s)
196
+ # elsif defined? LOGGER
197
+ # LOGGER.error(s)
198
+ # else
199
+ # puts s
200
+ # end
201
+ # end
202
+
203
+ def self.prepare_url(opts)
204
+ country = opts.delete(:country)
205
+ country = (country.nil?) ? 'us' : country
206
+ request_url = SERVICE_URLS[country.to_sym]
207
+ raise Amazon::RequestError, "Invalid country '#{country}'" unless request_url
208
+
209
+ qs = ''
210
+ opts.each {|k, v|
211
+ next unless v
212
+ v = v.join(',') if v.is_a? Array
213
+ qs << "&#{camelize(k.to_s)}=#{URI.encode(v.to_s)}"
214
+ }
215
+ url = "#{request_url}#{qs}"
216
+ # puts ">>> base url >> #{url}"
217
+ signed_url = sign_request(url)
218
+ # puts ">>> SIGNED >> #{signed_url}"
219
+ signed_url
220
+ end
221
+
222
+ def self.camelize(s)
223
+ s.to_s.gsub(/\/(.?)/) { '::' + Regexp.last_match[1].upcase }.gsub(/(^|_)(.)/) { Regexp.last_match[2].upcase }
224
+ end
225
+
226
+ def self.hmac_sha256(message, key)
227
+ block_size = 64
228
+ ipad = "\x36" * block_size
229
+ opad = "\x5c" * block_size
230
+ if key.size > block_size
231
+ d = Digest::SHA256.new
232
+ key = d.digest(key)
233
+ end
234
+
235
+ ipad_bytes = ipad.bytes.map { |b| b }
236
+ opad_bytes = opad.bytes.map { |b| b }
237
+ key_bytes = key.bytes.map { |b| b }
238
+ ipad_xor = ''
239
+ opad_xor = ''
240
+ for i in 0..key.size - 1
241
+ ipad_xor << (ipad_bytes[i] ^ key_bytes[i])
242
+ opad_xor << (opad_bytes[i] ^ key_bytes[i])
243
+ end
244
+
245
+ ipad = ipad_xor + ipad[key.size..-1]
246
+ opad = opad_xor + opad[key.size..-1]
247
+
248
+ # inner hash
249
+ d1 = Digest::SHA256.new
250
+ d1.update(ipad)
251
+ d1.update(message)
252
+ msg_hash = d1.digest
253
+
254
+ # outer hash
255
+ d2 = Digest::SHA256.new
256
+ d2.update(opad)
257
+ d2.update(msg_hash)
258
+ d2.digest
259
+ end
260
+
261
+ def self.sign_request(request)
262
+ raise AmazonNotConfiguredError unless @@secret_access_key
263
+ # Step 0 : Split apart request string
264
+ url_pattern = /http:\/\/([^\/]+)(\/[^\?]+)\?(.*$)/
265
+ url_pattern =~ request
266
+ host = Regexp.last_match[1]
267
+ path = Regexp.last_match[2]
268
+ param_string = Regexp.last_match[3]
269
+
270
+ # Step 1: enter the timestamp
271
+ t = Time.now.getutc # MUST be in UTC
272
+ stamp = t.strftime('%Y-%m-%dT%H:%M:%SZ')
273
+ param_string += "&Timestamp=#{stamp}"
274
+
275
+ # Step 2 : URL-encode
276
+ param_string = param_string.gsub(',', '%2C').gsub(':', '%3A')
277
+ # NOTE : take care not to double-encode
278
+
279
+ # Step 3 : Split the parameter/value pairs
280
+ params = param_string.split('&')
281
+
282
+ # Step 4 : Sort params
283
+ params.sort!
284
+
285
+ # Step 5 : Rejoin the param string
286
+ canonical_param_string = params.join('&')
287
+
288
+ # Steps 6 & 7: Prepend HTTP request info
289
+ string_to_sign = "GET\n#{host}\n#{path}\n#{canonical_param_string}"
290
+
291
+ # puts string_to_sign
292
+
293
+ # Step 8 : Calculate RFC 2104-compliant HMAC with SHA256 hash algorithm
294
+ sig = hmac_sha256(string_to_sign, @@secret_access_key)
295
+ base64_sig = [sig].pack('m').strip
296
+
297
+ # Step 9 : URL-encode + and = in sig
298
+ base64_sig = CGI.escape(base64_sig)
299
+
300
+ # Step 10 : Add the URL encoded signature to your request
301
+ "http://#{host}#{path}?#{param_string}&Signature=#{base64_sig}"
302
+ end
303
+ end
304
+
305
+ # Internal wrapper class to provide convenient method to access Hpricot element value.
306
+ class Element
307
+ # Pass Hpricot::Elements object
308
+ def initialize(element)
309
+ @element = element
310
+ end
311
+
312
+ # Returns Hpricot::Elments object
313
+ def elem
314
+ @element
315
+ end
316
+
317
+ # Find Hpricot::Elements matching the given path. Example: element/"author".
318
+ def /(path)
319
+ elements = @element / path
320
+ return nil if elements.size == 0
321
+ elements
322
+ end
323
+
324
+ # Find Hpricot::Elements matching the given path, and convert to Amazon::Element.
325
+ # Returns an array Amazon::Elements if more than Hpricot::Elements size is greater than 1.
326
+ def search_and_convert(path)
327
+ elements = self./(path)
328
+ return unless elements
329
+ elements = elements.map { |element| Element.new(element) }
330
+ return elements.first if elements.size == 1
331
+ elements
332
+ end
333
+
334
+ # Get the text value of the given path, leave empty to retrieve current element value.
335
+ def get(path = '')
336
+ Element.get(@element, path)
337
+ end
338
+
339
+ # Get the unescaped HTML text of the given path.
340
+ def get_unescaped(path = '')
341
+ Element.get_unescaped(@element, path)
342
+ end
343
+
344
+ # Get the array values of the given path.
345
+ def get_array(path = '')
346
+ Element.get_array(@element, path)
347
+ end
348
+
349
+ # Get the children element text values in hash format with the element names as the hash keys.
350
+ def get_hash(path = '')
351
+ Element.get_hash(@element, path)
352
+ end
353
+
354
+ # Similar to #get, except an element object must be passed-in.
355
+ def self.get(element, path = '')
356
+ return unless element
357
+ result = element.at(path)
358
+ ## inner_html doesn't decode entities, hence bug #21659
359
+ # result = result.inner_html if result
360
+ result = result.inner_text if result
361
+ result
362
+ end
363
+
364
+ # Similar to #get_unescaped, except an element object must be passed-in.
365
+ def self.get_unescaped(element, path = '')
366
+ result = get(element, path)
367
+ CGI.unescapeHTML(result) if result
368
+ end
369
+
370
+ # Similar to #get_array, except an element object must be passed-in.
371
+ def self.get_array(element, path = '')
372
+ return unless element
373
+
374
+ result = element / path
375
+ if (result.is_a? Hpricot::Elements) || (result.is_a? Array)
376
+ parsed_result = []
377
+ result.each {|item|
378
+ parsed_result << Element.get(item)
379
+ }
380
+ parsed_result
381
+ else
382
+ [Element.get(result)]
383
+ end
384
+ end
385
+
386
+ # Similar to #get_hash, except an element object must be passed-in.
387
+ def self.get_hash(element, path = '')
388
+ return unless element
389
+
390
+ result = element.at(path)
391
+ if result
392
+ hash = {}
393
+ result = result.children
394
+ result.each do |item|
395
+ hash[item.name.to_sym] = item.inner_html
396
+ end
397
+ hash
398
+ end
399
+ end
400
+
401
+ def to_s
402
+ elem.to_s if elem
403
+ end
404
+ end
405
+ end
@@ -0,0 +1,229 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # Copyright (C) 2009 Cathal Mc Ginley
4
+ # Modifications Copyright (C) 2011, 2014 Matijs van Zuijlen
5
+ #
6
+ # Alexandria is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License as
8
+ # published by the Free Software Foundation; either version 2 of the
9
+ # License, or (at your option) any later version.
10
+ #
11
+ # Alexandria is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public
17
+ # License along with Alexandria; see the file COPYING. If not,
18
+ # write to the Free Software Foundation, Inc., 51 Franklin Street,
19
+ # Fifth Floor, Boston, MA 02110-1301 USA.
20
+
21
+ # http://en.wikipedia.org/wiki/Barnes_&_Noble
22
+
23
+ # New BarnesAndNoble provider, taken from the Palatina MetaDataSource
24
+ # and modified to fit the structure of Alexandria book providers.
25
+ # Completely rewritten by Cathal Mc Ginley (18 Dec 2009)
26
+
27
+ # NOTE: this modified version is based on the Alexandria WorldCat provider.
28
+
29
+ require 'cgi'
30
+ require 'alexandria/net'
31
+ require 'alexandria/book_providers/web'
32
+
33
+ module Alexandria
34
+ class BookProviders
35
+ class BarnesAndNobleProvider < WebsiteBasedProvider
36
+ include Alexandria::Logging
37
+
38
+ SITE = 'http://www.barnesandnoble.com'
39
+
40
+ BASE_ISBN_SEARCH_URL = 'http://search.barnesandnoble.com/books' \
41
+ '/product.aspx?ISBSRC=Y&ISBN=%s'
42
+
43
+ BASE_SEARCH_URL = 'http://search.barnesandnoble.com/booksearch' \
44
+ '/results.asp?%s=%s' # type, term
45
+
46
+ def initialize
47
+ super('BarnesAndNoble', 'BarnesAndNoble')
48
+ @agent = nil
49
+ prefs.read
50
+ end
51
+
52
+ def agent
53
+ unless @agent
54
+ @agent = Alexandria::WWWAgent.new
55
+ end
56
+ @agent
57
+ end
58
+
59
+ def fetch_redirectly(uri_str, limit = 5)
60
+ raise NoResultsError, 'HTTP redirect too deep' if limit == 0
61
+ response = agent.get(uri_str)
62
+ if limit < 10
63
+ sleep 0.1
64
+ puts "Redirectly :: #{uri_str}"
65
+ else
66
+ puts "Fetching :: #{uri_str}"
67
+ end
68
+ puts response.inspect
69
+ case response
70
+ when Net::HTTPSuccess then response
71
+ when Net::HTTPRedirection then fetch_redirectly(response['Location'], (limit - 1))
72
+ else
73
+ response.error!
74
+ end
75
+ end
76
+
77
+ def search(criterion, type)
78
+ req = create_search_uri(type, criterion)
79
+ puts "Requesting #{req}" if $DEBUG
80
+ html_data = fetch_redirectly(req)
81
+
82
+ if type == SEARCH_BY_ISBN
83
+ parse_result_data(html_data.body, criterion)
84
+ else
85
+ results = parse_search_result_data(html_data.body)
86
+ raise NoResultsError if results.empty?
87
+
88
+ results.map { |result| get_book_from_search_result(result) }
89
+ end
90
+ end
91
+
92
+ def url(book)
93
+ create_search_uri(SEARCH_BY_ISBN, book.isbn)
94
+ rescue => ex
95
+ log.warn { "Cannot create url for book #{book}; #{ex.message}" }
96
+ nil
97
+ end
98
+
99
+ def create_search_uri(search_type, search_term)
100
+ search_type_code = {
101
+ SEARCH_BY_AUTHORS => 'ATH',
102
+ SEARCH_BY_TITLE => 'TTL',
103
+ SEARCH_BY_KEYWORD => 'WRD' # SEARCH_BY_PUBLISHER => 'PBL' # not implemented
104
+ }[search_type] or ''
105
+ if search_type == SEARCH_BY_ISBN
106
+ BASE_ISBN_SEARCH_URL % Library.canonicalise_ean(search_term) # isbn-13
107
+ else
108
+ search_term_encoded = CGI.escape(search_term)
109
+ BASE_SEARCH_URL % [search_type_code, search_term_encoded]
110
+ end
111
+ end
112
+
113
+ def get_book_from_search_result(result)
114
+ log.debug { "Fetching book from #{result[:url]}" }
115
+ html_data = transport.get_response(URI.parse(result[:url]))
116
+ parse_result_data(html_data.body)
117
+ end
118
+
119
+ def parse_search_result_data(html)
120
+ doc = html_to_doc(html)
121
+ book_search_results = []
122
+ begin
123
+ result_divs = doc / 'div[@class*="book-container"]'
124
+ result_divs.each do |div|
125
+ result = {}
126
+ # img = div % 'div.book-image/a/img'
127
+ # result[:image_url] = img['src'] if img
128
+ title_header = div % 'h2'
129
+ title_links = title_header / 'a'
130
+ result[:title] = title_links.first.inner_text
131
+ result[:url] = title_links.first['href']
132
+
133
+ book_search_results << result
134
+ end
135
+ rescue => ex
136
+ trace = ex.backtrace.join("\n> ")
137
+ log.warn {'Failed parsing search results for Barnes & Noble ' \
138
+ "#{ex.message} #{trace}" }
139
+ end
140
+ book_search_results
141
+ end
142
+
143
+ def parse_result_data(html, _search_isbn = nil, _recursing = false)
144
+ doc = html_to_doc(html)
145
+ begin
146
+ book_data = {}
147
+ title_header = doc % '//div.wgt-productTitle/h1'
148
+ if title_header
149
+ title = ''
150
+ title_header.children.each do |node|
151
+ if node.text?
152
+ title += ' ' + node.to_s
153
+ end
154
+ end
155
+ title.strip!
156
+ if title.empty?
157
+ log.warn { 'Unexpectedly found no title in BarnesAndNoble lookup' }
158
+ raise NoResultsError
159
+ end
160
+ book_data[:title] = title.strip.squeeze(' ')
161
+ subtitle_span = title_header % 'span.subtitle'
162
+ if subtitle_span
163
+ book_data[:title] += " #{subtitle_span.inner_text}"
164
+ end
165
+ end
166
+
167
+ isbn_links = doc / '//a.isbn-a'
168
+ isbns = isbn_links.map(&:inner_text)
169
+ book_data[:isbn] = Library.canonicalise_ean(isbns.first)
170
+
171
+ authors = []
172
+ author_links = title_header / 'a[@href*="ATH"]'
173
+ author_links.each do |a|
174
+ authors << a.inner_text
175
+ end
176
+ book_data[:authors] = authors
177
+
178
+ publisher_item = doc % 'li.publisher'
179
+ if publisher_item
180
+ publisher_item.inner_text =~ /Publisher:\s*(.+)/
181
+ book_data[:publisher] = Regexp.last_match[1]
182
+ end
183
+
184
+ date_item = doc % 'li.pubDate'
185
+ if date_item
186
+ date_item.inner_text =~ /Date: ([^\s]*)\s*([\d]{4})/
187
+ year = Regexp.last_match[2].to_i if Regexp.last_match[2]
188
+ book_data[:publication_year] = year
189
+ end
190
+
191
+ book_data[:binding] = ''
192
+ format_list_items = doc / '//div.col-one/ul/li'
193
+ format_list_items.each do |li|
194
+ if li.inner_text =~ /Format:\s*(.*),/
195
+ book_data[:binding] = Regexp.last_match[1]
196
+ end
197
+ end
198
+
199
+ product_image_div = doc % 'div#product-image'
200
+ if product_image_div
201
+ images = product_image_div / 'img'
202
+ if images.size == 1
203
+ book_data[:image_url] = images.first['src']
204
+ else
205
+ if images.first['src'] =~ /see_inside.gif/
206
+ # the first image is the "See Inside!" label
207
+ book_data[:image_url] = images[1]['src']
208
+ else
209
+ book_data[:image_url] = images.first['src']
210
+ end
211
+ end
212
+ end
213
+
214
+ book = Book.new(book_data[:title], book_data[:authors],
215
+ book_data[:isbn], book_data[:publisher],
216
+ book_data[:publication_year],
217
+ book_data[:binding])
218
+ return [book, book_data[:image_url]]
219
+ rescue => ex
220
+ raise ex if ex.instance_of? NoResultsError
221
+ trace = ex.backtrace.join("\n> ")
222
+ log.warn {'Failed parsing search results for BarnesAndNoble ' \
223
+ "#{ex.message} #{trace}" }
224
+ raise NoResultsError
225
+ end
226
+ end
227
+ end # class BarnesAndNobleProvider
228
+ end # class BookProviders
229
+ end # module Alexandria