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,316 @@
1
+ # Copyright (C) 2004-2006 Laurent Sansonetti
2
+ # Copyright (C) 2010 Cathal Mc Ginley
3
+ # Copyright (C) 2014 Matijs van Zuijlen
4
+ #
5
+ # Alexandria is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License as
7
+ # published by the Free Software Foundation; either version 2 of the
8
+ # License, or (at your option) any later version.
9
+ #
10
+ # Alexandria is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public
16
+ # License along with Alexandria; see the file COPYING. If not,
17
+ # write to the Free Software Foundation, Inc., 51 Franklin Street,
18
+ # Fifth Floor, Boston, MA 02110-1301 USA.
19
+ require 'gettext'
20
+
21
+ module Alexandria
22
+ class ImportFilter
23
+ attr_reader :name, :patterns, :message
24
+
25
+ include GetText
26
+ extend GetText
27
+ bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
28
+
29
+ def self.all
30
+ [
31
+ new(_('Autodetect'), ['*'], :import_autodetect),
32
+ new(_('Archived Tellico XML (*.bc, *.tc)'),
33
+ ['*.tc', '*.bc'], :import_as_tellico_xml_archive),
34
+ new(_('ISBN List (*.txt)'), ['*.txt'], :import_as_isbn_list),
35
+ new(_('GoodReads CSV'), ['*.csv'], :import_as_csv_file)
36
+ ]
37
+ end
38
+
39
+ def on_iterate(&on_iterate_cb)
40
+ @on_iterate_cb = on_iterate_cb
41
+ end
42
+
43
+ def on_error(&on_error_cb)
44
+ @on_error_cb = on_error_cb
45
+ end
46
+
47
+ def invoke(library_name, filename)
48
+ puts "Selected: #{@message} -- #{library_name} -- #{filename}"
49
+ Library.send(@message, library_name, filename,
50
+ @on_iterate_cb, @on_error_cb)
51
+ end
52
+
53
+ #######
54
+ private
55
+ #######
56
+
57
+ def initialize(name, patterns, message)
58
+ @name = name
59
+ @patterns = patterns
60
+ @message = message
61
+ end
62
+ end
63
+
64
+ class Library
65
+ def self.import_autodetect(*args)
66
+ puts args.inspect
67
+ filename = args[1]
68
+ puts "Filename is #{filename} and ext is #{filename[-4..-1]}"
69
+ puts "Beginning import: #{args[0]}, #{args[1]}"
70
+ if filename[-4..-1] == '.txt'
71
+ import_as_isbn_list(*args)
72
+ elsif ['.tc', '.bc'].include? filename[-3..-1]
73
+ begin
74
+ import_as_tellico_xml_archive(*args)
75
+ rescue => e
76
+ puts e.message
77
+ puts e.backtrace.join("\n>> ")
78
+ end
79
+ elsif ['.csv'].include? filename[-4..-1]
80
+ import_as_csv_file(*args)
81
+ else
82
+ puts 'Bailing on this import!'
83
+ raise 'Not supported type'
84
+ end
85
+ end
86
+
87
+ def self.import_as_tellico_xml_archive(name, filename,
88
+ on_iterate_cb, _on_error_cb)
89
+ puts 'Starting import_as_tellico_xml_archive... '
90
+ return nil unless system("unzip -qqt \"#{filename}\"")
91
+ tmpdir = File.join(Dir.tmpdir, 'tellico_export')
92
+ FileUtils.rm_rf(tmpdir) if File.exist?(tmpdir)
93
+ Dir.mkdir(tmpdir)
94
+ Dir.chdir(tmpdir) do
95
+ begin
96
+ system("unzip -qq \"#{filename}\"")
97
+ file = File.exist?('bookcase.xml') ? 'bookcase.xml' : 'tellico.xml'
98
+ xml = REXML::Document.new(File.open(file))
99
+ raise unless ['bookcase', 'tellico'].include? xml.root.name
100
+ # FIXME: handle multiple collections
101
+ raise unless xml.root.elements.size == 1
102
+ collection = xml.root.elements[1]
103
+ raise unless collection.name == 'collection'
104
+ type = collection.attribute('type').value.to_i
105
+ raise unless type == 2 or type == 5
106
+
107
+ content = []
108
+ entries = collection.elements.to_a('entry')
109
+ (total = entries.size).times do |n|
110
+ entry = entries[n]
111
+ elements = entry.elements
112
+ # Feed an array in here, tomorrow.
113
+ keys = ['isbn', 'publisher', 'pub_year', 'binding']
114
+
115
+ book_elements = [neaten(elements['title'].text)]
116
+ if !elements['authors'].nil?
117
+ book_elements += [elements['authors'].elements.to_a.map \
118
+ { |x| neaten(x.text) }]
119
+ else
120
+ book_elements += [[]]
121
+ end
122
+ book_elements += keys.map {|key|
123
+ if elements[key]
124
+ neaten(elements[key].text)
125
+ else
126
+ nil
127
+ end
128
+ }
129
+ # isbn
130
+ if book_elements[2].nil? or book_elements[2].strip.empty?
131
+ book_elements[2] = nil
132
+ else
133
+ begin
134
+ book_elements[2] = book_elements[2].strip
135
+ book_elements[2] = Library.canonicalise_ean(book_elements[2])
136
+ rescue => ex
137
+ puts book_elements[2]
138
+ puts ex.message
139
+ puts ex.backtrace.join("\n> ")
140
+ raise ex
141
+ end
142
+ end
143
+ book_elements[4] = book_elements[4].to_i unless book_elements[4].nil? # publishing_year
144
+ puts book_elements.inspect
145
+ if elements['cover']
146
+ cover = neaten(elements['cover'].text)
147
+ else
148
+ cover = nil
149
+ end
150
+ puts cover
151
+ book = Book.new(*book_elements)
152
+ if elements['rating'] and (0..UI::MainApp::MAX_RATING_STARS).map.member? elements['rating'].text.to_i
153
+ book.rating = elements['rating'].text.to_i
154
+ end
155
+ book.notes = neaten(elements['comments'].text) if elements['comments']
156
+ content << [book, cover]
157
+ on_iterate_cb.call(n + 1, total) if on_iterate_cb
158
+ end
159
+
160
+ library = Library.load(name)
161
+ content.each do |book, cover|
162
+ unless cover.nil?
163
+ library.save_cover(book,
164
+ File.join(Dir.pwd, 'images',
165
+ cover))
166
+ end
167
+ library << book
168
+ library.save(book)
169
+ end
170
+ return [library, []]
171
+ rescue => e
172
+ puts e.message
173
+ return nil
174
+ end
175
+ end
176
+ end
177
+
178
+ def self.import_as_csv_file(name, filename, on_iterate_cb, _on_error_cb)
179
+ require 'alexandria/import_library_csv'
180
+ books_and_covers = []
181
+ line_count = IO.readlines(filename).reduce(0) { |count, _line| count + 1 }
182
+
183
+ import_count = 0
184
+ max_import = line_count - 1
185
+
186
+ reader = CSV.open(filename, 'r')
187
+ # Goodreads & LibraryThing now use csv header lines
188
+ header = reader.shift
189
+ importer = identify_csv_type(header)
190
+ failed_once = false
191
+ begin
192
+ reader.each do |row|
193
+ book = importer.row_to_book(row)
194
+ cover = nil
195
+ if book.isbn
196
+ # if we can search by ISBN, try to grab the cover
197
+ begin
198
+ dl_book, dl_cover = Alexandria::BookProviders.isbn_search(book.isbn)
199
+ if dl_book.authors.size > book.authors.size
200
+ # LibraryThing only supports a single author, so
201
+ # attempt to include more author information if it's
202
+ # available
203
+ book.authors = dl_book.authors
204
+ end
205
+ unless book.edition
206
+ book.edition = dl_book.edition
207
+ end
208
+ cover = dl_cover
209
+ rescue
210
+ puts "failed to get cover for #{book.title} #{book.isbn}" if $DEBUG
211
+ # note failure
212
+ end
213
+ end
214
+
215
+ books_and_covers << [book, cover]
216
+ import_count += 1
217
+ if on_iterate_cb
218
+ on_iterate_cb.call(import_count, max_import)
219
+ end
220
+
221
+ end
222
+ rescue CSV::IllegalFormatError
223
+ unless failed_once
224
+ failed_once = true
225
+
226
+ # probably Goodreads' wonky ISBN fields ,,="043432432X",
227
+ # this is a hack to fix up such files
228
+ data = File.read(filename)
229
+ data.gsub!(/\,\=\"/, ',"')
230
+ csv_fixed = Tempfile.new('alexandria_import_csv_fixed_')
231
+ csv_fixed.write(data)
232
+ csv_fixed.close
233
+
234
+ reader = CSV.open(csv_fixed.path, 'r')
235
+ header = reader.shift
236
+ importer = identify_csv_type(header)
237
+
238
+ retry
239
+ end
240
+ end
241
+
242
+ library = Library.load(name)
243
+
244
+ books_and_covers.each do |book, cover_uri|
245
+ puts "Saving #{book.isbn} cover..." if $DEBUG
246
+ library.save_cover(book, cover_uri) unless cover_uri.nil?
247
+ puts "Saving #{book.isbn}..." if $DEBUG
248
+ library << book
249
+ library.save(book)
250
+ end
251
+ [library, []]
252
+ end
253
+
254
+ def self.import_as_isbn_list(name, filename, on_iterate_cb,
255
+ on_error_cb)
256
+ puts 'Starting import_as_isbn_list... '
257
+ isbn_list = IO.readlines(filename).map do |line|
258
+ puts "Trying line #{line}" if $DEBUG
259
+ # Let's preserve the failing isbns so we can report them later.
260
+ begin
261
+ [line.chomp, canonicalise_isbn(line.chomp)] unless line == "\n"
262
+ rescue => e
263
+ puts e.message
264
+ [line.chomp, nil]
265
+ end
266
+ end
267
+ puts "Isbn list: #{isbn_list.inspect}"
268
+ isbn_list.compact!
269
+ return nil if isbn_list.empty?
270
+ max_iterations = isbn_list.length * 2
271
+ current_iteration = 1
272
+ books = []
273
+ bad_isbns = []
274
+ failed_lookup_isbns = []
275
+ isbn_list.each do |isbn|
276
+ begin
277
+ if isbn[1]
278
+ books << Alexandria::BookProviders.isbn_search(isbn[1])
279
+ else
280
+ bad_isbns << isbn[0]
281
+ end
282
+ rescue => e
283
+ puts e.message
284
+ failed_lookup_isbns << isbn[1]
285
+ puts "NOTE : ignoring on_error_cb #{on_error_cb}"
286
+ # return nil unless
287
+ # (on_error_cb and on_error_cb.call(e.message))
288
+ end
289
+
290
+ on_iterate_cb.call(current_iteration += 1,
291
+ max_iterations) if on_iterate_cb
292
+ end
293
+ puts "Bad Isbn list: #{bad_isbns.inspect}" if bad_isbns
294
+ library = load(name)
295
+ puts "Going with these #{books.length} books: #{books.inspect}" if $DEBUG
296
+ books.each do |book, cover_uri|
297
+ puts "Saving #{book.isbn} cover..." if $DEBUG
298
+ library.save_cover(book, cover_uri) unless cover_uri.nil?
299
+ puts "Saving #{book.isbn}..." if $DEBUG
300
+ library << book
301
+ library.save(book)
302
+ on_iterate_cb.call(current_iteration += 1,
303
+ max_iterations) if on_iterate_cb
304
+ end
305
+ [library, bad_isbns, failed_lookup_isbns]
306
+ end
307
+
308
+ def self.neaten(str)
309
+ if str
310
+ str.strip
311
+ else
312
+ str
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,270 @@
1
+ # Copyright (C) 2010 Cathal Mc Ginley
2
+ #
3
+ # Alexandria is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU General Public License as
5
+ # published by the Free Software Foundation; either version 2 of the
6
+ # License, or (at your option) any later version.
7
+ #
8
+ # Alexandria is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public
14
+ # License along with Alexandria; see the file COPYING. If not,
15
+ # write to the Free Software Foundation, Inc., 51 Franklin Street,
16
+ # Fifth Floor, Boston, MA 02110-1301 USA.
17
+
18
+ # New CSV import code taken from Palatina and modified for
19
+ # Alexandria. (29 Apr 2010)
20
+
21
+ require 'csv'
22
+ require 'date'
23
+ require 'htmlentities'
24
+ require 'tempfile'
25
+
26
+ module Alexandria
27
+ class CSVImport
28
+ def initialize(header)
29
+ @header = header
30
+ @html = HTMLEntities.new
31
+ end
32
+
33
+ def index_of(header_name)
34
+ @header.each_with_index do |h, i|
35
+ if h == header_name
36
+ return i
37
+ end
38
+ end
39
+ -1
40
+ end
41
+
42
+ def normalize(string)
43
+ @html.decode(string).strip
44
+ end
45
+ end
46
+
47
+ class GoodreadsCSVImport < CSVImport
48
+ def initialize(header)
49
+ super(header)
50
+ @title = index_of('Title')
51
+ @author = index_of('Author')
52
+ @additional_authors = index_of('Additional Authors')
53
+ @isbn = index_of('ISBN')
54
+ @publisher = index_of('Publisher')
55
+ @publishing_year = index_of('Year Published')
56
+ @edition = index_of('Binding')
57
+
58
+ # optional extras
59
+ @notes = index_of('Private Notes')
60
+ @rating = index_of('My Rating')
61
+ @read_count = index_of('Read Count')
62
+ @date_read = index_of('Date Read')
63
+ @bookshelves = index_of('Bookshelves') # save names as tags
64
+ @mainshelf = index_of('Exclusive Shelf')
65
+ end
66
+
67
+ def row_to_book(row)
68
+ title = normalize(row[@title])
69
+ authors = []
70
+ authors << normalize(row[@author])
71
+ additional = row[@additional_authors]
72
+ additional.split(',').each do |add|
73
+ authors << normalize(add)
74
+ end
75
+ isbn = row[@isbn] # TODO canonicalize_ean...
76
+ if isbn
77
+ isbn = Library.canonicalise_ean(isbn)
78
+ end
79
+ publisher = normalize(row[@publisher])
80
+ year = row[@publishing_year].to_i
81
+ edition = normalize(row[@edition])
82
+ book = Alexandria::Book.new(title,
83
+ authors,
84
+ isbn,
85
+ publisher,
86
+ year,
87
+ edition)
88
+ if row[@notes]
89
+ book.notes = normalize(row[@notes])
90
+ end
91
+ if row[@rating]
92
+ book.rating = row[@rating].to_i
93
+ end
94
+ if row[@read_count]
95
+ count = row[@read_count].to_i
96
+ if count > 0
97
+ book.redd = true
98
+ end
99
+ end
100
+ if row[@date_read]
101
+ begin
102
+ date = Date.strptime(str, '%d/%m/%y') # e.g. "14/01/10" => 2010-01-14
103
+ book.redd_when = date
104
+ book.redd = true
105
+ rescue
106
+ #
107
+ end
108
+ end
109
+ if row[@mainshelf]
110
+ if row[@mainshelf] == 'read'
111
+ book.redd = true
112
+ elsif row[@mainshelf] == 'to-read'
113
+ book.redd = false
114
+ book.tags = ['to read']
115
+ elsif row[@mainshelf] == 'currently-reading'
116
+ book.redd = false
117
+ book.tags = ['currently reading']
118
+ end
119
+ end
120
+ if row[@bookshelves]
121
+ shelves = normalize(row[@bookshelves]).split
122
+ shelves.each do |shelf|
123
+ tag = shelf.gsub(/-/, ' ')
124
+ unless book.tags.include? tag
125
+ book.tags << tag
126
+ end
127
+ end
128
+ end
129
+ puts "Goodreads loading #{book.title}" if $DEBUG
130
+ book
131
+ end
132
+ end
133
+
134
+ class LibraryThingCSVImport < CSVImport
135
+ def initialize(header)
136
+ super(header)
137
+ @title = index_of("'TITLE'")
138
+ @author = index_of("'AUTHOR (first, last)'")
139
+ @isbn = index_of("'ISBN'")
140
+ @publisher_info = index_of("'PUBLICATION INFO'")
141
+ @publishing_year = index_of("'DATE'")
142
+
143
+ # optional extras
144
+ @notes = index_of("'COMMENTS'")
145
+ @rating = index_of("'RATING'")
146
+ @tags = index_of("'TAGS'")
147
+ end
148
+
149
+ def row_to_book(row)
150
+ title = normalize(row[@title])
151
+ authors = []
152
+ authors << normalize(row[@author])
153
+ isbn = row[@isbn]
154
+ if isbn
155
+ if isbn =~ /\[([^\]]+)\]/
156
+ isbn = Regexp.last_match[1]
157
+ end
158
+ isbn = Library.canonicalise_ean(isbn)
159
+ end
160
+
161
+ # usually "Publisher (YEAR), Binding, NUM pages"
162
+ # sometimes "Publisher (YEAR), Edition: NUM, Binding, NUM pages"
163
+ publisher_info = normalize(row[@publisher_info])
164
+ publisher = publisher_info
165
+ if publisher_info =~ /([^\(]+)\(/
166
+ publisher = Regexp.last_match[1]
167
+ end
168
+ edition = publisher_info # binding
169
+ edition_info = publisher_info.split(',')
170
+ if edition_info.size >= 3
171
+ edition = publisher_info.split(',')[-2]
172
+ end
173
+
174
+ year = row[@publishing_year].to_i
175
+
176
+ book = Alexandria::Book.new(title,
177
+ authors,
178
+ isbn,
179
+ publisher,
180
+ year,
181
+ edition)
182
+ if row[@notes]
183
+ book.notes = normalize(row[@notes])
184
+ end
185
+
186
+ if row[@rating]
187
+ book.rating = row[@rating].to_i
188
+ end
189
+ if row[@tags]
190
+ tags = normalize(row[@tags]).split(',')
191
+ tags.each do |tag|
192
+ unless book.tags.include? tag
193
+ book.tags << tag
194
+ end
195
+ end
196
+ end
197
+
198
+ puts "LibraryThing loading #{book.title}" if $DEBUG
199
+ book
200
+ end
201
+ end
202
+
203
+ class Library
204
+ # LibraryThing has 15 fields (Apr 2010), Goodreads has 29
205
+ # we shall guess that "PUBLICATION INFO" implies LibraryThing
206
+ # and "Year Published" implies Goodreads
207
+
208
+ def self.identify_csv_type(header)
209
+ is_librarything = false
210
+ is_goodreads = false
211
+ header.each do |head|
212
+ if head == "'PUBLICATION INFO'"
213
+ is_librarything = true
214
+ break
215
+ elsif head == 'Year Published'
216
+ is_goodreads = true
217
+ break
218
+ end
219
+ end
220
+ if is_librarything
221
+ return LibraryThingCSVImport.new(header)
222
+ elsif is_goodreads
223
+ return GoodreadsCSVImport.new(header)
224
+ end
225
+ raise 'Not Recognized' unless is_librarything || is_goodreads
226
+ end
227
+ end
228
+
229
+ # def read_csv_file(csv_file)
230
+
231
+ # reader = CSV.open(csv_file, 'r')
232
+ # # goodreads & librarything now use csv header lines
233
+ # header = reader.shift
234
+
235
+ # #header.each {|h| puts h }
236
+ # importer = identify_csv_type(header)
237
+
238
+ # retries = 0
239
+ # begin
240
+ # reader.each do |row|
241
+ # #puts row
242
+ # bk = importer.row_to_book(row)
243
+ # puts "\"#{bk.title}\" by #{bk.authors.join(', ')}; isbn : #{bk.isbn} #{bk.publisher} | #{bk.publishing_year.inspect} binding:#{bk.edition}"
244
+ # if bk.notes.size > 0
245
+ # puts " >>> #{bk.notes}"
246
+ # end
247
+ # end
248
+ # rescue
249
+ # if retries < 1
250
+ # retries += 1
251
+ # puts "Retrying..."
252
+
253
+ # # probably Goodreads' wonky ISBN fields ,,="043432432X", and such
254
+ # # hack to fix up these files
255
+ # data = File.read(csv_file)
256
+ # data.gsub!(/\,\=\"/, ',"')
257
+ # csv_fixed = Tempfile.new("#{csv_file}_fixed")
258
+ # csv_fixed.write(data)
259
+ # csv_fixed.close
260
+ # reader = CSV.open(csv_fixed.path, 'r')
261
+ # #puts csv_fixed.path
262
+ # header = reader.shift
263
+
264
+ # retry
265
+ # end
266
+
267
+ # end
268
+
269
+ # end
270
+ end