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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +68 -0
- data/.rubocop_todo.yml +200 -0
- data/CHANGELOG.md +23 -0
- data/COPYING +339 -0
- data/ChangeLog.0 +3598 -0
- data/Gemfile +9 -0
- data/INSTALL.rdoc +156 -0
- data/PACKAGING +36 -0
- data/README.md +88 -0
- data/RELEASE_CHECKLIST +18 -0
- data/Rakefile +264 -0
- data/TODO +24 -0
- data/alexandria-book-collection-manager.gemspec +35 -0
- data/alexandria.desktop.in +10 -0
- data/bin/alexandria +82 -0
- data/doc/AUTHORS +60 -0
- data/doc/BUGS +31 -0
- data/doc/FAQ +369 -0
- data/doc/HACKING +19 -0
- data/doc/NEWS +341 -0
- data/doc/alexandria.1 +120 -0
- data/doc/cuecat_support.rdoc +67 -0
- data/lib/alexandria.rb +85 -0
- data/lib/alexandria/about.rb +82 -0
- data/lib/alexandria/book_providers.rb +411 -0
- data/lib/alexandria/book_providers/adlibris.rb +235 -0
- data/lib/alexandria/book_providers/amazon_aws.rb +261 -0
- data/lib/alexandria/book_providers/amazon_ecs_util.rb +405 -0
- data/lib/alexandria/book_providers/barnes_and_noble.rb +229 -0
- data/lib/alexandria/book_providers/bol_it.rb +162 -0
- data/lib/alexandria/book_providers/deastore.rb +277 -0
- data/lib/alexandria/book_providers/douban.rb +135 -0
- data/lib/alexandria/book_providers/ibs_it.rb +149 -0
- data/lib/alexandria/book_providers/mcu.rb +177 -0
- data/lib/alexandria/book_providers/proxis.rb +205 -0
- data/lib/alexandria/book_providers/pseudomarc.rb +185 -0
- data/lib/alexandria/book_providers/renaud.rb +142 -0
- data/lib/alexandria/book_providers/siciliano.rb +271 -0
- data/lib/alexandria/book_providers/thalia.rb +197 -0
- data/lib/alexandria/book_providers/web.rb +59 -0
- data/lib/alexandria/book_providers/webster_it.rb +173 -0
- data/lib/alexandria/book_providers/worldcat.rb +251 -0
- data/lib/alexandria/book_providers/z3950.rb +422 -0
- data/lib/alexandria/config.rb +8 -0
- data/lib/alexandria/console.rb +31 -0
- data/lib/alexandria/execution_queue.rb +96 -0
- data/lib/alexandria/export_library.rb +536 -0
- data/lib/alexandria/import_library.rb +316 -0
- data/lib/alexandria/import_library_csv.rb +270 -0
- data/lib/alexandria/logging.rb +159 -0
- data/lib/alexandria/models/book.rb +72 -0
- data/lib/alexandria/models/library.rb +714 -0
- data/lib/alexandria/net.rb +53 -0
- data/lib/alexandria/preferences.rb +324 -0
- data/lib/alexandria/scanners.rb +42 -0
- data/lib/alexandria/scanners/cuecat.rb +118 -0
- data/lib/alexandria/scanners/keyboard.rb +57 -0
- data/lib/alexandria/smart_library.rb +525 -0
- data/lib/alexandria/ui.rb +53 -0
- data/lib/alexandria/ui/builder_base.rb +43 -0
- data/lib/alexandria/ui/callbacks.rb +389 -0
- data/lib/alexandria/ui/completion_models.rb +228 -0
- data/lib/alexandria/ui/dialogs/about_dialog.rb +59 -0
- data/lib/alexandria/ui/dialogs/acquire_dialog.rb +640 -0
- data/lib/alexandria/ui/dialogs/alert_dialog.rb +68 -0
- data/lib/alexandria/ui/dialogs/bad_isbns_dialog.rb +43 -0
- data/lib/alexandria/ui/dialogs/barcode_animation.rb +159 -0
- data/lib/alexandria/ui/dialogs/book_properties_dialog.rb +210 -0
- data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +432 -0
- data/lib/alexandria/ui/dialogs/export_dialog.rb +172 -0
- data/lib/alexandria/ui/dialogs/import_dialog.rb +205 -0
- data/lib/alexandria/ui/dialogs/misc_dialogs.rb +85 -0
- data/lib/alexandria/ui/dialogs/new_book_dialog.rb +639 -0
- data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +153 -0
- data/lib/alexandria/ui/dialogs/new_smart_library_dialog.rb +67 -0
- data/lib/alexandria/ui/dialogs/preferences_dialog.rb +587 -0
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog.rb +56 -0
- data/lib/alexandria/ui/dialogs/smart_library_properties_dialog_base.rb +432 -0
- data/lib/alexandria/ui/dndable.rb +81 -0
- data/lib/alexandria/ui/gtk_thread_help.rb +88 -0
- data/lib/alexandria/ui/icons.rb +100 -0
- data/lib/alexandria/ui/iconview.rb +91 -0
- data/lib/alexandria/ui/iconview_tooltips.rb +162 -0
- data/lib/alexandria/ui/init.rb +93 -0
- data/lib/alexandria/ui/libraries_combo.rb +75 -0
- data/lib/alexandria/ui/listview.rb +310 -0
- data/lib/alexandria/ui/main_app.rb +67 -0
- data/lib/alexandria/ui/multi_drag_treeview.rb +150 -0
- data/lib/alexandria/ui/sidepane.rb +194 -0
- data/lib/alexandria/ui/sound.rb +107 -0
- data/lib/alexandria/ui/ui_manager.rb +1308 -0
- data/lib/alexandria/undo_manager.rb +78 -0
- data/lib/alexandria/utils.rb +30 -0
- data/lib/alexandria/version.rb +24 -0
- data/lib/alexandria/web_themes.rb +75 -0
- data/misc/sounds/README +15 -0
- data/misc/sounds/bad_scan.csd +62 -0
- data/misc/sounds/good_scan.csd +61 -0
- data/misc/sounds/scanning.csd +46 -0
- data/po/ChangeLog +488 -0
- data/po/Makefile +44 -0
- data/po/README +29 -0
- data/po/commit-po +72 -0
- data/po/cs.po +1437 -0
- data/po/cy.po +1521 -0
- data/po/de.po +1400 -0
- data/po/el.po +1379 -0
- data/po/es.po +1376 -0
- data/po/fr.po +1420 -0
- data/po/ga.po +1359 -0
- data/po/gl.po +1397 -0
- data/po/it.po +1406 -0
- data/po/ja.po +1355 -0
- data/po/mk.po +1373 -0
- data/po/nb.po +1386 -0
- data/po/nl.po +1405 -0
- data/po/pl.po +1373 -0
- data/po/pt.po +1398 -0
- data/po/pt_BR.po +1409 -0
- data/po/ru.po +1372 -0
- data/po/sk.po +1380 -0
- data/po/sv.po +1402 -0
- data/po/uk.po +1423 -0
- data/po/zh_TW.po +1394 -0
- data/schemas/alexandria.schemas +300 -0
- data/share/alexandria/glade/acquire_dialog__builder.glade +201 -0
- data/share/alexandria/glade/book_properties_dialog__builder.glade +910 -0
- data/share/alexandria/glade/main_app__builder.glade +229 -0
- data/share/alexandria/glade/new_book_dialog__builder.glade +379 -0
- data/share/alexandria/glade/preferences_dialog__builder.glade +733 -0
- data/share/alexandria/icons/alexandria.png +0 -0
- data/share/alexandria/icons/alexandria_small.png +0 -0
- data/share/alexandria/icons/book.png +0 -0
- data/share/alexandria/icons/book_icon.png +0 -0
- data/share/alexandria/icons/book_small.png +0 -0
- data/share/alexandria/icons/cuecat.png +0 -0
- data/share/alexandria/icons/cuecat_inactive.png +0 -0
- data/share/alexandria/icons/favorite_tag.png +0 -0
- data/share/alexandria/icons/less.png +0 -0
- data/share/alexandria/icons/library.png +0 -0
- data/share/alexandria/icons/library_small.png +0 -0
- data/share/alexandria/icons/lookup.png +0 -0
- data/share/alexandria/icons/more.png +0 -0
- data/share/alexandria/icons/no_cover.png +0 -0
- data/share/alexandria/icons/smart_library.png +0 -0
- data/share/alexandria/icons/smart_library_small.png +0 -0
- data/share/alexandria/icons/star_set.png +0 -0
- data/share/alexandria/icons/star_unset.png +0 -0
- data/share/alexandria/icons/view_as_icons.png +0 -0
- data/share/alexandria/icons/view_as_list.png +0 -0
- data/share/alexandria/ui/menus.xml +91 -0
- data/share/alexandria/ui/popups.xml +91 -0
- data/share/alexandria/web-themes/clean/clean.css +85 -0
- data/share/alexandria/web-themes/clean/preview.jpg +0 -0
- data/share/alexandria/web-themes/list/list.css +105 -0
- data/share/alexandria/web-themes/list/preview.jpg +0 -0
- data/share/app-icon/16x16/alexandria.png +0 -0
- data/share/app-icon/16x16/alexandria.svg +263 -0
- data/share/app-icon/22x22/alexandria.png +0 -0
- data/share/app-icon/22x22/alexandria.svg +465 -0
- data/share/app-icon/24x24/alexandria.png +0 -0
- data/share/app-icon/32x32/alexandria.png +0 -0
- data/share/app-icon/32x32/alexandria.svg +813 -0
- data/share/app-icon/32x32/alexandria.xpm +241 -0
- data/share/app-icon/48x48/alexandria.png +0 -0
- data/share/app-icon/scalable/alexandria.svg +700 -0
- data/share/gnome/help/alexandria/C/about.xml +44 -0
- data/share/gnome/help/alexandria/C/adding-books.xml +339 -0
- data/share/gnome/help/alexandria/C/alexandria.xml +185 -0
- data/share/gnome/help/alexandria/C/bugs.xml +18 -0
- data/share/gnome/help/alexandria/C/editing-book-properties.xml +124 -0
- data/share/gnome/help/alexandria/C/exporting.xml +81 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_acquire_from_scanner_process.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_add_by_isbn.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_isbn_import.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_manual_details.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_rename_library_after_import.png +0 -0
- data/share/gnome/help/alexandria/C/figures/adding_books_search_results.png +0 -0
- data/share/gnome/help/alexandria/C/figures/editing_book_properties_info.png +0 -0
- data/share/gnome/help/alexandria/C/figures/editing_book_properties_loaning.png +0 -0
- data/share/gnome/help/alexandria/C/figures/exporting_information_html.png +0 -0
- data/share/gnome/help/alexandria/C/figures/getting_started_first_launched.png +0 -0
- data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view.png +0 -0
- data/share/gnome/help/alexandria/C/figures/searching_filtering_views_list_view_search.png +0 -0
- data/share/gnome/help/alexandria/C/figures/settings_providers_new_z3950.png +0 -0
- data/share/gnome/help/alexandria/C/figures/smart_libraries_new_smart_library.png +0 -0
- data/share/gnome/help/alexandria/C/figures/working_with_libraries_library_pane.png +0 -0
- data/share/gnome/help/alexandria/C/getting-started.xml +154 -0
- data/share/gnome/help/alexandria/C/gnu-fdl-1.2.xml +543 -0
- data/share/gnome/help/alexandria/C/introduction.xml +142 -0
- data/share/gnome/help/alexandria/C/searching.xml +90 -0
- data/share/gnome/help/alexandria/C/settings.xml +140 -0
- data/share/gnome/help/alexandria/C/smart-libraries.xml +160 -0
- data/share/gnome/help/alexandria/C/working-with-libraries.xml +76 -0
- data/share/gnome/help/alexandria/ChangeLog +99 -0
- data/share/gnome/help/alexandria/fr/alexandria.xml +2292 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_button.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_isbn_1.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_by_search_1.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_manually.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_add_z3950.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_close_button.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_edit_info.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_export_web_page.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_importing.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_library_pane.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_list_view.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_list_view_search.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_loaning.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_main_window.png +0 -0
- data/share/gnome/help/alexandria/fr/figures/alexandria_remove_button.png +0 -0
- data/share/gnome/help/alexandria/ja/about.xml +33 -0
- data/share/gnome/help/alexandria/ja/adding-books.xml +314 -0
- data/share/gnome/help/alexandria/ja/alexandria.xml +172 -0
- data/share/gnome/help/alexandria/ja/bugs.xml +11 -0
- data/share/gnome/help/alexandria/ja/editing-book-properties.xml +100 -0
- data/share/gnome/help/alexandria/ja/exporting.xml +98 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_acquire_from_scanner_process.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_add_by_isbn.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_isbn_import.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_manual_details.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_rename_library_after_import.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/adding_books_search_results.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/editing_book_properties_info.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/editing_book_properties_loaning.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/exporting_information_html.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/getting_started_first_launched.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/searching_filtering_views_list_view_search.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/settings_providers_new_z3950.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/smart_libraries_new_smart_library.png +0 -0
- data/share/gnome/help/alexandria/ja/figures/working_with_libraries_library_pane.png +0 -0
- data/share/gnome/help/alexandria/ja/getting-started.xml +144 -0
- data/share/gnome/help/alexandria/ja/gnu-fdl-1.2.xml +541 -0
- data/share/gnome/help/alexandria/ja/introduction.xml +134 -0
- data/share/gnome/help/alexandria/ja/searching.xml +104 -0
- data/share/gnome/help/alexandria/ja/settings.xml +129 -0
- data/share/gnome/help/alexandria/ja/smart-libraries.xml +140 -0
- data/share/gnome/help/alexandria/ja/working-with-libraries.xml +88 -0
- data/share/menu/alexandria +7 -0
- data/share/omf/alexandria/alexandria-C.omf.in +27 -0
- data/share/omf/alexandria/alexandria-fr.omf.in +30 -0
- data/share/sounds/alexandria/bad_scan.ogg +0 -0
- data/share/sounds/alexandria/bad_scan.wav +0 -0
- data/share/sounds/alexandria/good_scan.ogg +0 -0
- data/share/sounds/alexandria/good_scan.wav +0 -0
- data/share/sounds/alexandria/scanning.ogg +0 -0
- data/share/sounds/alexandria/scanning.wav +0 -0
- data/spec/alexandria/library_spec.rb +205 -0
- data/spec/alexandria/preferences_spec.rb +22 -0
- data/spec/alexandria/scanners/cuecat_spec.rb +68 -0
- data/spec/alexandria/smart_library_spec.rb +22 -0
- data/spec/alexandria/ui/dialogs_spec.rb +90 -0
- data/spec/alexandria/ui/iconview_spec.rb +27 -0
- data/spec/alexandria/ui/listview_spec.rb +28 -0
- data/spec/alexandria/ui/main_app_spec.rb +48 -0
- data/spec/alexandria/ui/sidepane_spec.rb +27 -0
- data/spec/alexandria/ui/ui_manager_spec.rb +26 -0
- data/spec/alexandria/ui/ui_utilities_spec.rb +60 -0
- data/spec/alexandria/utilities_spec.rb +50 -0
- data/spec/data/libraries/0.6.1-noisbn/My Library/0201398257.yaml +10 -0
- data/spec/data/libraries/0.6.1-noisbn/My Library/1565920007.yaml +10 -0
- data/spec/data/libraries/0.6.1/My Library/0192812173.yaml +9 -0
- data/spec/data/libraries/0.6.1/My Library/0201398257.cover +0 -0
- data/spec/data/libraries/0.6.1/My Library/0201398257.yaml +10 -0
- data/spec/data/libraries/0.6.1/My Library/1565920007.yaml +10 -0
- data/spec/data/libraries/0.6.2/My Library/9780140266146.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780140266146.yaml +16 -0
- data/spec/data/libraries/0.6.2/My Library/9780140278781.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780140278781.yaml +21 -0
- data/spec/data/libraries/0.6.2/My Library/9780571147168.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780571147168.yaml +20 -0
- data/spec/data/libraries/0.6.2/My Library/9780575079038.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780575079038.yaml +20 -0
- data/spec/data/libraries/0.6.2/My Library/9780755322800.cover +0 -0
- data/spec/data/libraries/0.6.2/My Library/9780755322800.yaml +20 -0
- data/spec/spec_helper.rb +46 -0
- data/tasks/rdoc.rake +6 -0
- data/tasks/setup.rb +30 -0
- data/tasks/spec.rake +29 -0
- data/tasks/test.rake +38 -0
- data/test/application_test.rb +39 -0
- data/test/book_test.rb +34 -0
- data/test/data/isbns.txt +3 -0
- data/test/isbn_test.rb +68 -0
- data/test/providers_test.rb +254 -0
- data/test/test_helper.rb +42 -0
- data/util/rake/fileinstall.rb +313 -0
- data/util/rake/gettextgenerate.rb +158 -0
- data/util/rake/omfgenerate.rb +79 -0
- metadata +452 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
# Copyright (C) 2007 Cathal Mc Ginley
|
2
|
+
# Modifications Copyright (C) 2011 Matijs van Zuijlen
|
3
|
+
#
|
4
|
+
# Alexandria is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU General Public License as
|
6
|
+
# published by the Free Software Foundation; either version 2 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# Alexandria is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public
|
15
|
+
# License along with Alexandria; see the file COPYING. If not,
|
16
|
+
# write to the Free Software Foundation, Inc., 51 Franklin Street,
|
17
|
+
# Fifth Floor, Boston, MA 02110-1301 USA.
|
18
|
+
|
19
|
+
require 'logger'
|
20
|
+
require 'forwardable'
|
21
|
+
|
22
|
+
module Alexandria
|
23
|
+
# A Logger subclass which accepts a source for log messages
|
24
|
+
# in order to improve legibility of the logs.
|
25
|
+
# The source should usually be +self+, whether that be a Class, Module
|
26
|
+
# or Object. A LoggerWrapper can be used to simplify this procedure.
|
27
|
+
class Logger < ::Logger
|
28
|
+
def add(severity, message = nil, source = nil, progname = nil, &block)
|
29
|
+
if source.nil?
|
30
|
+
return super(severity, message, progname, &block)
|
31
|
+
end
|
32
|
+
category = self.class.category(source)
|
33
|
+
unless block_given?
|
34
|
+
return super(severity, progname, category)
|
35
|
+
end
|
36
|
+
category = "#{category} #{progname}" if progname
|
37
|
+
super(severity, message, category, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def debug(source = nil, progname = nil, &block)
|
41
|
+
add(DEBUG, nil, source, progname, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def info(source = nil, progname = nil, &block)
|
45
|
+
add(INFO, nil, source, progname, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def warn(source = nil, progname = nil, &block)
|
49
|
+
add(WARN, nil, source, progname, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def error(source = nil, progname = nil, &block)
|
53
|
+
add(ERROR, nil, source, progname, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def fatal(source = nil, progname = nil, &block)
|
57
|
+
add(FATAL, nil, source, progname, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.category(source)
|
61
|
+
if source.instance_of? Class
|
62
|
+
"[Cls #{source.name}]"
|
63
|
+
elsif source.instance_of? Module
|
64
|
+
"[Mod #{source.name}]"
|
65
|
+
else
|
66
|
+
"<Obj #{source.class.name}>"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# A wrapper around a Logger, which allows code to define the source
|
72
|
+
# once (in the wrapper's initialization) and then call the log methods
|
73
|
+
# whithout needing to specify the source each time.
|
74
|
+
class LogWrapper
|
75
|
+
extend Forwardable
|
76
|
+
def initialize(logger, source)
|
77
|
+
@logger = logger
|
78
|
+
@source = source
|
79
|
+
end
|
80
|
+
|
81
|
+
def_delegators :@logger, :debug?, :info?, :warn?, :error?, :fatal?
|
82
|
+
|
83
|
+
def <<(msg)
|
84
|
+
if msg.respond_to? :backtrace
|
85
|
+
msg.backtrace.each { |line|
|
86
|
+
@logger << " #{line} \n"
|
87
|
+
}
|
88
|
+
else
|
89
|
+
@logger << msg + "\n"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def debug(progname = nil, &block)
|
94
|
+
@logger.debug(@source, progname, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
def info(progname = nil, &block)
|
98
|
+
@logger.info(@source, progname, &block)
|
99
|
+
end
|
100
|
+
|
101
|
+
def warn(progname = nil, &block)
|
102
|
+
@logger.warn(@source, progname, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
def error(progname = nil, &block)
|
106
|
+
@logger.error(@source, progname, &block)
|
107
|
+
end
|
108
|
+
|
109
|
+
def fatal(progname = nil, &block)
|
110
|
+
@logger.fatal(@source, progname, &block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# A mixin to include a +log+ instance method for objects or a
|
115
|
+
# static +log+ method for classes and modules. In either case, a
|
116
|
+
# +LogWrapper+ is returned which wraps the Alexandria log and
|
117
|
+
# specifies the appropriate source object, class or module.
|
118
|
+
module Logging
|
119
|
+
module ClassMethods
|
120
|
+
def log
|
121
|
+
@log_wrapper ||= LogWrapper.new(Alexandria.log, self)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.included(base)
|
126
|
+
base.extend(ClassMethods)
|
127
|
+
end
|
128
|
+
|
129
|
+
def log
|
130
|
+
@log_wrapper ||= LogWrapper.new(Alexandria.log, self)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# Creates the Logger for Alexandria
|
137
|
+
def self.create_logger
|
138
|
+
logger = Alexandria::Logger.new(STDERR)
|
139
|
+
|
140
|
+
level = ENV['LOGLEVEL'] ? ENV['LOGLEVEL'].intern : nil
|
141
|
+
if [:FATAL, :ERROR, :WARN, :INFO, :DEBUG].include? level
|
142
|
+
logger.level = Logger.const_get(level)
|
143
|
+
else
|
144
|
+
logger.level = Logger::WARN # default level
|
145
|
+
logger.warn(self, "Unknown LOGLEVEL '#{level}'; using WARN") if level
|
146
|
+
end
|
147
|
+
|
148
|
+
logger
|
149
|
+
end
|
150
|
+
|
151
|
+
@@logger = create_logger
|
152
|
+
|
153
|
+
public
|
154
|
+
|
155
|
+
# Returns the Logger for Alexandria
|
156
|
+
def self.log
|
157
|
+
@@logger
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright (C) 2004-2006 Laurent Sansonetti
|
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
|
+
module Alexandria
|
19
|
+
class Book
|
20
|
+
attr_accessor :title, :authors, :isbn, :publisher, :publishing_year,
|
21
|
+
:edition, :rating, :notes, :loaned, :loaned_since,
|
22
|
+
:loaned_to, :saved_ident, :redd, :redd_when, :own, :want, :tags, :version, :library
|
23
|
+
|
24
|
+
DEFAULT_RATING = 0
|
25
|
+
|
26
|
+
def initialize(title, authors, isbn, publisher, publishing_year,
|
27
|
+
edition)
|
28
|
+
|
29
|
+
@title = title
|
30
|
+
@authors = authors
|
31
|
+
@isbn = isbn
|
32
|
+
@publisher = publisher
|
33
|
+
@edition = edition # actually used for binding! (i.e. paperback or hardback)
|
34
|
+
@notes = ''
|
35
|
+
@saved_ident = ident
|
36
|
+
@publishing_year = publishing_year
|
37
|
+
@redd = false
|
38
|
+
@own = true
|
39
|
+
@want = true
|
40
|
+
@tags = []
|
41
|
+
# Need to implement bulk save function to update this
|
42
|
+
@version = Alexandria::DATA_VERSION
|
43
|
+
end
|
44
|
+
|
45
|
+
def ident
|
46
|
+
if (!@isbn.nil?) and @isbn.empty?
|
47
|
+
@isbn = nil
|
48
|
+
end
|
49
|
+
@isbn or @title.hash.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def loaned?
|
53
|
+
loaned or false
|
54
|
+
end
|
55
|
+
|
56
|
+
def redd?
|
57
|
+
redd or false
|
58
|
+
end
|
59
|
+
|
60
|
+
def want?
|
61
|
+
want or false
|
62
|
+
end
|
63
|
+
|
64
|
+
def own?
|
65
|
+
own or false
|
66
|
+
end
|
67
|
+
|
68
|
+
def ==(obj)
|
69
|
+
obj.is_a?(self.class) and ident == obj.ident
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,714 @@
|
|
1
|
+
# Copyright (C) 2004-2006 Laurent Sansonetti
|
2
|
+
# Copyright (C) 2014 Matijs van Zuijlen
|
3
|
+
#
|
4
|
+
# Alexandria is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU General Public License as
|
6
|
+
# published by the Free Software Foundation; either version 2 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# Alexandria is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public
|
15
|
+
# License along with Alexandria; see the file COPYING. If not,
|
16
|
+
# write to the Free Software Foundation, Inc., 51 Franklin Street,
|
17
|
+
# Fifth Floor, Boston, MA 02110-1301 USA.
|
18
|
+
|
19
|
+
require 'yaml'
|
20
|
+
require 'fileutils'
|
21
|
+
require 'rexml/document'
|
22
|
+
require 'tempfile'
|
23
|
+
require 'etc'
|
24
|
+
require 'open-uri'
|
25
|
+
require 'observer'
|
26
|
+
require 'singleton'
|
27
|
+
|
28
|
+
class Array
|
29
|
+
def sum
|
30
|
+
reduce(0) { |a, b| a + b }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Alexandria
|
35
|
+
class Library < Array
|
36
|
+
include Logging
|
37
|
+
|
38
|
+
attr_reader :name
|
39
|
+
attr_accessor :ruined_books, :updating, :deleted_books
|
40
|
+
DIR = File.join(ENV['HOME'], '.alexandria')
|
41
|
+
EXT = { book: '.yaml', cover: '.cover' }
|
42
|
+
|
43
|
+
include GetText
|
44
|
+
extend GetText
|
45
|
+
bindtextdomain(Alexandria::TEXTDOMAIN, charset: 'UTF-8')
|
46
|
+
|
47
|
+
BOOK_ADDED, BOOK_UPDATED, BOOK_REMOVED = (0..3).to_a
|
48
|
+
include Observable
|
49
|
+
|
50
|
+
def path
|
51
|
+
File.join(DIR, @name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def updating?
|
55
|
+
@updating
|
56
|
+
end
|
57
|
+
def self.generate_new_name(existing_libraries,
|
58
|
+
from_base = _('Untitled'))
|
59
|
+
i = 1
|
60
|
+
name = nil
|
61
|
+
all_libraries = existing_libraries + @@deleted_libraries
|
62
|
+
while true
|
63
|
+
name = i == 1 ? from_base : from_base + " #{i}"
|
64
|
+
break unless all_libraries.find { |x| x.name == name }
|
65
|
+
i += 1
|
66
|
+
end
|
67
|
+
name
|
68
|
+
end
|
69
|
+
|
70
|
+
FIX_BIGNUM_REGEX =
|
71
|
+
/loaned_since:\s*(\!ruby\/object\:Bignum\s*)?(\d+)\n/
|
72
|
+
|
73
|
+
def self.load(name)
|
74
|
+
test = [0, nil]
|
75
|
+
ruined_books = []
|
76
|
+
library = Library.new(name)
|
77
|
+
FileUtils.mkdir_p(library.path) unless File.exist?(library.path)
|
78
|
+
Dir.chdir(library.path) do
|
79
|
+
Dir['*' + EXT[:book]].each do |filename|
|
80
|
+
|
81
|
+
test[1] = filename if test[0] == 0
|
82
|
+
|
83
|
+
unless File.size? test[1]
|
84
|
+
log.warn { "Book file #{test[1]} was empty" }
|
85
|
+
md = /([\dxX]{10,13})#{EXT[:book]}/.match(filename)
|
86
|
+
if md
|
87
|
+
file_isbn = md[1]
|
88
|
+
ruined_books << [nil, file_isbn, library]
|
89
|
+
else
|
90
|
+
log.warn { "Filename #{filename} does not contain an ISBN" }
|
91
|
+
# TODO delete this file...
|
92
|
+
end
|
93
|
+
next
|
94
|
+
end
|
95
|
+
book = regularize_book_from_yaml(test[1])
|
96
|
+
old_isbn = book.isbn
|
97
|
+
old_pub_year = book.publishing_year
|
98
|
+
begin
|
99
|
+
begin
|
100
|
+
book.isbn = canonicalise_ean(book.isbn).to_s unless book.isbn.nil?
|
101
|
+
raise "Not a book: #{book.inspect}" unless book.is_a?(Book)
|
102
|
+
rescue InvalidISBNError
|
103
|
+
book.isbn = old_isbn
|
104
|
+
end
|
105
|
+
|
106
|
+
book.publishing_year = book.publishing_year.to_i unless book.publishing_year.nil?
|
107
|
+
|
108
|
+
# Or if isbn has changed
|
109
|
+
raise "#{test[1]} isbn is not okay" unless book.isbn == old_isbn
|
110
|
+
|
111
|
+
# Re-save book if Alexandria::DATA_VERSION changes
|
112
|
+
raise "#{test[1]} version is not okay" unless book.version == Alexandria::DATA_VERSION
|
113
|
+
|
114
|
+
# Or if publishing year has changed
|
115
|
+
raise "#{test[1]} pub year is not okay" unless book.publishing_year == old_pub_year
|
116
|
+
|
117
|
+
# ruined_books << [book, book.isbn, library]
|
118
|
+
book.library = library.name
|
119
|
+
|
120
|
+
## TODO copy cover image file, if necessary
|
121
|
+
# due to #26909 cover files for books without ISBN are re-saved as "g#{ident}.cover"
|
122
|
+
if book.isbn.nil? || book.isbn.empty?
|
123
|
+
if File.exist? library.old_cover(book)
|
124
|
+
log.debug { "#{library.name}; book #{book.title} has no ISBN, fixing cover image" }
|
125
|
+
FileUtils::Verbose.mv(library.old_cover(book), library.cover(book))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
library << book
|
130
|
+
rescue
|
131
|
+
book.version = Alexandria::DATA_VERSION
|
132
|
+
savedfilename = library.simple_save(book)
|
133
|
+
test[0] = test[0] + 1
|
134
|
+
test[1] = savedfilename
|
135
|
+
|
136
|
+
# retries the Dir.each block...
|
137
|
+
# but gives up after three tries
|
138
|
+
redo unless test[0] > 2
|
139
|
+
|
140
|
+
else
|
141
|
+
test = [0, nil]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Since 0.4.0 the cover files '_small.jpg' and
|
146
|
+
# '_medium.jpg' have been deprecated for a single medium
|
147
|
+
# cover file named '.cover'.
|
148
|
+
|
149
|
+
Dir['*' + '_medium.jpg'].each do |medium_cover|
|
150
|
+
begin
|
151
|
+
FileUtils.mv(medium_cover,
|
152
|
+
medium_cover.sub(/_medium\.jpg$/,
|
153
|
+
EXT[:cover]))
|
154
|
+
rescue
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
Dir['*' + EXT[:cover]].each do |cover|
|
159
|
+
next if cover[0] == 'g'
|
160
|
+
md = /(.+)\.cover/.match(cover)
|
161
|
+
begin
|
162
|
+
ean = canonicalise_ean(md[1])
|
163
|
+
rescue
|
164
|
+
ean = md[1]
|
165
|
+
end
|
166
|
+
begin
|
167
|
+
FileUtils.mv(cover, ean + EXT[:cover]) unless cover == ean + EXT[:cover]
|
168
|
+
rescue
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
FileUtils.rm_f(Dir['*_small.jpg'])
|
173
|
+
end
|
174
|
+
library.ruined_books = ruined_books
|
175
|
+
|
176
|
+
library
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.regularize_book_from_yaml(name)
|
180
|
+
text = IO.read(name)
|
181
|
+
|
182
|
+
# Code to remove the mystery string in books imported from Amazon
|
183
|
+
# (In the past, still?) To allow ruby-amazon to be removed.
|
184
|
+
|
185
|
+
# The string is removed on load, but can't make it stick, maybe has to do with cache
|
186
|
+
|
187
|
+
if /!str:Amazon::Search::Response/.match(text)
|
188
|
+
log.debug { "Removing Ruby/Amazon strings from #{name}" }
|
189
|
+
text.gsub!('!str:Amazon::Search::Response', '')
|
190
|
+
end
|
191
|
+
|
192
|
+
# Backward compatibility with versions <= 0.6.0, where the
|
193
|
+
# loaned_since field was a numeric.
|
194
|
+
if (md = FIX_BIGNUM_REGEX.match(text))
|
195
|
+
new_yaml = Time.at(md[2].to_i).to_yaml
|
196
|
+
# Remove the "---" prefix.
|
197
|
+
new_yaml.sub!(/^\s*\-+\s*/, '')
|
198
|
+
text.sub!(md[0], "loaned_since: #{new_yaml}\n")
|
199
|
+
end
|
200
|
+
book = YAML.load(text)
|
201
|
+
unless book.isbn.class == String
|
202
|
+
# HACK
|
203
|
+
md = /isbn: (.+)/.match(text)
|
204
|
+
if md
|
205
|
+
string_isbn = md[1].strip
|
206
|
+
book.isbn = string_isbn
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# another HACK of the same type as above
|
211
|
+
unless book.saved_ident.class == String
|
212
|
+
|
213
|
+
md2 = /saved_ident: (.+)/.match(text)
|
214
|
+
if md2
|
215
|
+
string_saved_ident = md2[1].strip
|
216
|
+
log.debug { "fixing saved_ident #{book.saved_ident} -> #{string_saved_ident}" }
|
217
|
+
book.saved_ident = string_saved_ident
|
218
|
+
end
|
219
|
+
end
|
220
|
+
if book.isbn.class == String and book.isbn.length == 0
|
221
|
+
book.isbn = nil # save trouble later
|
222
|
+
end
|
223
|
+
book
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.loadall
|
227
|
+
a = []
|
228
|
+
begin
|
229
|
+
Dir.entries(DIR).each do |file|
|
230
|
+
# Skip hidden files.
|
231
|
+
next if /^\./.match(file)
|
232
|
+
# Skip non-directory files.
|
233
|
+
next unless File.stat(File.join(DIR, file)).directory?
|
234
|
+
|
235
|
+
a << load(file)
|
236
|
+
end
|
237
|
+
|
238
|
+
rescue Errno::ENOENT
|
239
|
+
FileUtils.mkdir_p(DIR)
|
240
|
+
end
|
241
|
+
# Create the default library if there is no library yet.
|
242
|
+
|
243
|
+
if a.empty?
|
244
|
+
a << load(_('My Library'))
|
245
|
+
end
|
246
|
+
|
247
|
+
a
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.move(source_library, dest_library, *books)
|
251
|
+
dest = dest_library.path
|
252
|
+
books.each do |book|
|
253
|
+
FileUtils.mv(source_library.yaml(book), dest)
|
254
|
+
if File.exist?(source_library.cover(book))
|
255
|
+
FileUtils.mv(source_library.cover(book), dest)
|
256
|
+
end
|
257
|
+
|
258
|
+
source_library.changed
|
259
|
+
source_library.old_delete(book)
|
260
|
+
source_library.notify_observers(source_library,
|
261
|
+
BOOK_REMOVED,
|
262
|
+
book)
|
263
|
+
|
264
|
+
dest_library.changed
|
265
|
+
dest_library.delete_if { |book2| book2.ident == book.ident }
|
266
|
+
dest_library << book
|
267
|
+
dest_library.notify_observers(dest_library, BOOK_ADDED, book)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class NoISBNError < StandardError
|
272
|
+
def initialize(msg)
|
273
|
+
super(msg)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
class InvalidISBNError < StandardError
|
278
|
+
attr_reader :isbn
|
279
|
+
def initialize(isbn = nil)
|
280
|
+
super()
|
281
|
+
@isbn = isbn
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def self.extract_numbers(isbn)
|
286
|
+
raise NoISBNError.new('Nil ISBN') if isbn.nil? || isbn.empty?
|
287
|
+
|
288
|
+
isbn.delete('- ').upcase.split('').map do |x|
|
289
|
+
raise InvalidISBNError.new(isbn) unless x =~ /[\dX]/
|
290
|
+
x == 'X' ? 10 : x.to_i
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.isbn_checksum(numbers)
|
295
|
+
sum = (0...numbers.length).reduce(0) do |accumulator, i|
|
296
|
+
accumulator + numbers[i] * (i + 1)
|
297
|
+
end % 11
|
298
|
+
|
299
|
+
sum == 10 ? 'X' : sum
|
300
|
+
end
|
301
|
+
|
302
|
+
def self.valid_isbn?(isbn)
|
303
|
+
numbers = extract_numbers(isbn)
|
304
|
+
numbers.length == 10 and isbn_checksum(numbers) == 0
|
305
|
+
rescue InvalidISBNError
|
306
|
+
false
|
307
|
+
end
|
308
|
+
|
309
|
+
def self.ean_checksum(numbers)
|
310
|
+
(10 - ([1, 3, 5, 7, 9, 11].map { |x| numbers[x] }.sum * 3 +
|
311
|
+
[0, 2, 4, 6, 8, 10].map { |x| numbers[x] }.sum)) % 10
|
312
|
+
end
|
313
|
+
|
314
|
+
def self.valid_ean?(ean)
|
315
|
+
numbers = extract_numbers(ean)
|
316
|
+
(numbers.length == 13 and
|
317
|
+
ean_checksum(numbers[0..11]) == numbers[12]) or
|
318
|
+
(numbers.length == 18 and
|
319
|
+
ean_checksum(numbers[0..11]) == numbers[12])
|
320
|
+
rescue InvalidISBNError
|
321
|
+
false
|
322
|
+
end
|
323
|
+
|
324
|
+
def self.upc_checksum(numbers)
|
325
|
+
(10 - ([0, 2, 4, 6, 8, 10].map { |x| numbers[x] }.sum * 3 +
|
326
|
+
[1, 3, 5, 7, 9].map { |x| numbers[x] }.sum)) % 10
|
327
|
+
end
|
328
|
+
|
329
|
+
def self.valid_upc?(upc)
|
330
|
+
numbers = extract_numbers(upc)
|
331
|
+
(numbers.length == 17 and
|
332
|
+
upc_checksum(numbers[0..10]) == numbers[11])
|
333
|
+
rescue InvalidISBNError
|
334
|
+
false
|
335
|
+
end
|
336
|
+
|
337
|
+
AMERICAN_UPC_LOOKUP = {
|
338
|
+
'014794' => '08041', '018926' => '0445', '02778' => '0449',
|
339
|
+
'037145' => '0812', '042799' => '0785', '043144' => '0688',
|
340
|
+
'044903' => '0312', '045863' => '0517', '046594' => '0064',
|
341
|
+
'047132' => '0152', '051487' => '08167', '051488' => '0140',
|
342
|
+
'060771' => '0002', '065373' => '0373', '070992' => '0523',
|
343
|
+
'070993' => '0446', '070999' => '0345', '071001' => '0380',
|
344
|
+
'071009' => '0440', '071125' => '088677', '071136' => '0451',
|
345
|
+
'071149' => '0451', '071152' => '0515', '071162' => '0451',
|
346
|
+
'071268' => '08217', '071831' => '0425', '071842' => '08439',
|
347
|
+
'072742' => '0441', '076714' => '0671', '076783' => '0553',
|
348
|
+
'076814' => '0449', '078021' => '0872', '079808' => '0394',
|
349
|
+
'090129' => '0679', '099455' => '0061', '099769' => '0451'
|
350
|
+
}
|
351
|
+
|
352
|
+
def self.upc_convert(upc)
|
353
|
+
test_upc = upc.map(&:to_s).join
|
354
|
+
extract_numbers(AMERICAN_UPC_LOOKUP[test_upc])
|
355
|
+
end
|
356
|
+
|
357
|
+
def self.canonicalise_ean(code)
|
358
|
+
code = code.to_s.delete('- ')
|
359
|
+
if self.valid_ean?(code)
|
360
|
+
return code
|
361
|
+
elsif self.valid_isbn?(code)
|
362
|
+
code = '978' + code[0..8]
|
363
|
+
return code + String(ean_checksum(extract_numbers(code)))
|
364
|
+
elsif self.valid_upc?(code)
|
365
|
+
isbn10 = canonicalise_isbn
|
366
|
+
code = '978' + isbn10[0..8]
|
367
|
+
return code + String(ean_checksum(extract_numbers(code)))
|
368
|
+
## raise "fix function Alexandria::Library.canonicalise_ean"
|
369
|
+
else
|
370
|
+
raise InvalidISBNError.new(code)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def self.canonicalise_isbn(isbn)
|
375
|
+
numbers = extract_numbers(isbn)
|
376
|
+
if self.valid_ean?(isbn) and numbers[0..2] != [9, 7, 8]
|
377
|
+
return isbn
|
378
|
+
end
|
379
|
+
canonical = if self.valid_ean?(isbn)
|
380
|
+
# Looks like an EAN number -- extract the intersting part and
|
381
|
+
# calculate a checksum. It would be nice if we could validate
|
382
|
+
# the EAN number somehow.
|
383
|
+
numbers[3..11] + [isbn_checksum(numbers[3..11])]
|
384
|
+
elsif self.valid_upc?(isbn)
|
385
|
+
# Seems to be a valid UPC number.
|
386
|
+
prefix = upc_convert(numbers[0..5])
|
387
|
+
isbn_sans_chcksm = prefix + numbers[(8 + prefix.length)..17]
|
388
|
+
isbn_sans_chcksm + [isbn_checksum(isbn_sans_chcksm)]
|
389
|
+
elsif self.valid_isbn?(isbn)
|
390
|
+
# Seems to be a valid ISBN number.
|
391
|
+
numbers[0..-2] + [isbn_checksum(numbers[0..-2])]
|
392
|
+
else
|
393
|
+
raise InvalidISBNError.new(isbn)
|
394
|
+
end
|
395
|
+
|
396
|
+
canonical.map(&:to_s).join
|
397
|
+
end
|
398
|
+
|
399
|
+
def simple_save(book)
|
400
|
+
# Let's initialize the saved identifier if not already
|
401
|
+
# (backward compatibility from 0.4.0)
|
402
|
+
# book.saved_ident ||= book.ident
|
403
|
+
if book.saved_ident.nil? or book.saved_ident.empty?
|
404
|
+
book.saved_ident = book.ident
|
405
|
+
end
|
406
|
+
if book.ident != book.saved_ident
|
407
|
+
# log.debug { "Backwards compatibility step: #{book.saved_ident.inspect}, #{book.ident.inspect}" }
|
408
|
+
FileUtils.rm(yaml(book.saved_ident))
|
409
|
+
end
|
410
|
+
if File.exist?(cover(book.saved_ident))
|
411
|
+
begin
|
412
|
+
FileUtils.mv(cover(book.saved_ident), cover(book.ident))
|
413
|
+
rescue
|
414
|
+
end
|
415
|
+
end
|
416
|
+
book.saved_ident = book.ident
|
417
|
+
|
418
|
+
filename = book.saved_ident.to_s + '.yaml'
|
419
|
+
File.open(filename, 'w') { |io| io.puts book.to_yaml }
|
420
|
+
filename
|
421
|
+
end
|
422
|
+
|
423
|
+
def save(book, final = false)
|
424
|
+
changed unless final
|
425
|
+
|
426
|
+
# Let's initialize the saved identifier if not already
|
427
|
+
# (backward compatibility from 0.4.0).
|
428
|
+
book.saved_ident ||= book.ident
|
429
|
+
|
430
|
+
if book.ident != book.saved_ident
|
431
|
+
FileUtils.rm(yaml(book.saved_ident))
|
432
|
+
if File.exist?(cover(book.saved_ident))
|
433
|
+
FileUtils.mv(cover(book.saved_ident), cover(book.ident))
|
434
|
+
end
|
435
|
+
|
436
|
+
# Notify before updating the saved identifier, so the views
|
437
|
+
# can still use the old one to update their models.
|
438
|
+
notify_observers(self, BOOK_UPDATED, book) unless final
|
439
|
+
book.saved_ident = book.ident
|
440
|
+
end
|
441
|
+
# #was File.exist? but that returns true for empty files... CathalMagus
|
442
|
+
already_there = (File.size?(yaml(book)) and
|
443
|
+
!@deleted_books.include?(book))
|
444
|
+
|
445
|
+
temp_book = book.dup
|
446
|
+
temp_book.library = nil
|
447
|
+
File.open(yaml(temp_book), 'w') { |io| io.puts temp_book.to_yaml }
|
448
|
+
|
449
|
+
# Do not notify twice.
|
450
|
+
if changed?
|
451
|
+
notify_observers(self,
|
452
|
+
already_there ? BOOK_UPDATED : BOOK_ADDED,
|
453
|
+
book)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def transport
|
458
|
+
config = Alexandria::Preferences.instance.http_proxy_config
|
459
|
+
config ? Net::HTTP.Proxy(*config) : Net::HTTP
|
460
|
+
end
|
461
|
+
|
462
|
+
def save_cover(book, cover_uri)
|
463
|
+
Dir.chdir(path) do
|
464
|
+
# Fetch the cover picture.
|
465
|
+
cover_file = cover(book)
|
466
|
+
File.open(cover_file, 'w') do |io|
|
467
|
+
uri = URI.parse(cover_uri)
|
468
|
+
if uri.scheme.nil?
|
469
|
+
# Regular filename.
|
470
|
+
File.open(cover_uri) { |io2| io.puts io2.read }
|
471
|
+
else
|
472
|
+
# Try open-uri.
|
473
|
+
io.puts transport.get(uri)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# Remove the file if its blank.
|
478
|
+
if Alexandria::UI::Icons.blank?(cover_file)
|
479
|
+
File.delete(cover_file)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
@@deleted_libraries = []
|
485
|
+
|
486
|
+
def self.deleted_libraries
|
487
|
+
@@deleted_libraries
|
488
|
+
end
|
489
|
+
|
490
|
+
def self.really_delete_deleted_libraries
|
491
|
+
@@deleted_libraries.each do |library|
|
492
|
+
FileUtils.rm_rf(library.path)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
def really_delete_deleted_books
|
497
|
+
@deleted_books.each do |book|
|
498
|
+
[yaml(book), cover(book)].each do |file|
|
499
|
+
FileUtils.rm_f(file)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
alias_method :old_delete, :delete
|
505
|
+
def delete(book = nil)
|
506
|
+
if book.nil?
|
507
|
+
# Delete the whole library.
|
508
|
+
raise if @@deleted_libraries.include?(self)
|
509
|
+
@@deleted_libraries << self
|
510
|
+
else
|
511
|
+
if @deleted_books.include?(book)
|
512
|
+
doubles = @deleted_books.reject { |b| !b.equal?(book) }
|
513
|
+
raise "Book #{book.isbn} was already deleted" unless doubles.empty?
|
514
|
+
end
|
515
|
+
@deleted_books << book
|
516
|
+
i = index(book)
|
517
|
+
# We check object IDs there because the user could have added
|
518
|
+
# a book with the same identifier as another book he/she
|
519
|
+
# previously deleted and that he/she is trying to redo.
|
520
|
+
if i and self[i].equal? book
|
521
|
+
changed
|
522
|
+
old_delete(book) # FIX this will old_delete all '==' books
|
523
|
+
notify_observers(self, BOOK_REMOVED, book)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
def deleted?
|
529
|
+
@@deleted_libraries.include?(self)
|
530
|
+
end
|
531
|
+
|
532
|
+
def undelete(book = nil)
|
533
|
+
if book.nil?
|
534
|
+
# Undelete the whole library.
|
535
|
+
raise unless @@deleted_libraries.include?(self)
|
536
|
+
@@deleted_libraries.delete(self)
|
537
|
+
else
|
538
|
+
raise unless @deleted_books.include?(book)
|
539
|
+
@deleted_books.delete(book)
|
540
|
+
unless self.include?(book)
|
541
|
+
changed
|
542
|
+
self << book
|
543
|
+
notify_observers(self, BOOK_ADDED, book)
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
alias_method :old_select, :select
|
549
|
+
def select
|
550
|
+
filtered_library = Library.new(@name)
|
551
|
+
each do |book|
|
552
|
+
filtered_library << book if yield(book)
|
553
|
+
end
|
554
|
+
filtered_library
|
555
|
+
end
|
556
|
+
|
557
|
+
def old_cover(book)
|
558
|
+
File.join(path, book.ident.to_s + EXT[:cover])
|
559
|
+
end
|
560
|
+
|
561
|
+
def cover(something)
|
562
|
+
ident = case something
|
563
|
+
when Book
|
564
|
+
if something.isbn && !something.isbn.empty?
|
565
|
+
something.ident
|
566
|
+
else
|
567
|
+
"g#{something.ident}" # g is for generated id...
|
568
|
+
end
|
569
|
+
when String
|
570
|
+
something
|
571
|
+
when Bignum
|
572
|
+
something
|
573
|
+
when Fixnum
|
574
|
+
something
|
575
|
+
else
|
576
|
+
raise "#{something} is a #{something.class}"
|
577
|
+
end
|
578
|
+
File.join(path, ident.to_s + EXT[:cover])
|
579
|
+
end
|
580
|
+
|
581
|
+
def yaml(something, basedir = path)
|
582
|
+
ident = case something
|
583
|
+
when Book
|
584
|
+
something.ident
|
585
|
+
when String
|
586
|
+
something
|
587
|
+
when Bignum
|
588
|
+
something
|
589
|
+
when Fixnum
|
590
|
+
something
|
591
|
+
else
|
592
|
+
raise "#{something} is #{something.class}"
|
593
|
+
end
|
594
|
+
File.join(basedir, ident.to_s + EXT[:book])
|
595
|
+
end
|
596
|
+
|
597
|
+
def name=(name)
|
598
|
+
File.rename(path, File.join(DIR, name))
|
599
|
+
@name = name
|
600
|
+
end
|
601
|
+
|
602
|
+
def n_rated
|
603
|
+
select { |x| !x.rating.nil? and x.rating > 0 }.length
|
604
|
+
end
|
605
|
+
|
606
|
+
def n_unrated
|
607
|
+
length - n_rated
|
608
|
+
end
|
609
|
+
|
610
|
+
def ==(object)
|
611
|
+
object.is_a?(self.class) && object.name == name
|
612
|
+
end
|
613
|
+
|
614
|
+
def copy_covers(somewhere)
|
615
|
+
FileUtils.rm_rf(somewhere) if File.exist?(somewhere)
|
616
|
+
FileUtils.mkdir(somewhere)
|
617
|
+
each do |book|
|
618
|
+
next unless File.exist?(cover(book))
|
619
|
+
FileUtils.cp(cover(book),
|
620
|
+
File.join(somewhere, final_cover(book)))
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
def self.jpeg?(file)
|
625
|
+
'JFIF' == IO.read(file, 10)[6..9]
|
626
|
+
end
|
627
|
+
|
628
|
+
def final_cover(book)
|
629
|
+
# TODO what about PNG?
|
630
|
+
book.ident + (Library.jpeg?(cover(book)) ? '.jpg' : '.gif')
|
631
|
+
end
|
632
|
+
|
633
|
+
#########
|
634
|
+
protected
|
635
|
+
#########
|
636
|
+
|
637
|
+
def initialize(name)
|
638
|
+
@name = name
|
639
|
+
@deleted_books = []
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
class Libraries
|
644
|
+
attr_reader :all_libraries, :ruined_books, :deleted_books
|
645
|
+
|
646
|
+
include Observable
|
647
|
+
include Singleton
|
648
|
+
|
649
|
+
def reload
|
650
|
+
@all_libraries.clear
|
651
|
+
@all_libraries.concat(Library.loadall)
|
652
|
+
@all_libraries.concat(SmartLibrary.loadall)
|
653
|
+
|
654
|
+
ruined = []
|
655
|
+
deleted = []
|
656
|
+
all_regular_libraries.each {|library|
|
657
|
+
ruined += library.ruined_books
|
658
|
+
# make deleted books from each library accessible so we don't crash on smart libraries
|
659
|
+
deleted += library.deleted_books
|
660
|
+
}
|
661
|
+
@ruined_books = ruined
|
662
|
+
@deleted_books = deleted
|
663
|
+
end
|
664
|
+
|
665
|
+
def all_regular_libraries
|
666
|
+
@all_libraries.select { |x| x.is_a?(Library) }
|
667
|
+
end
|
668
|
+
|
669
|
+
def all_smart_libraries
|
670
|
+
@all_libraries.select { |x| x.is_a?(SmartLibrary) }
|
671
|
+
end
|
672
|
+
|
673
|
+
# def all_dynamic_libraries
|
674
|
+
# @all_libraries.select { |x| x.is_a?(SmartLibrary) }
|
675
|
+
# end
|
676
|
+
|
677
|
+
LIBRARY_ADDED, LIBRARY_REMOVED = 1, 2
|
678
|
+
|
679
|
+
def add_library(library)
|
680
|
+
@all_libraries << library
|
681
|
+
notify(LIBRARY_ADDED, library)
|
682
|
+
end
|
683
|
+
|
684
|
+
def remove_library(library)
|
685
|
+
@all_libraries.delete(library)
|
686
|
+
notify(LIBRARY_REMOVED, library)
|
687
|
+
end
|
688
|
+
|
689
|
+
def really_delete_deleted_libraries
|
690
|
+
Library.really_delete_deleted_libraries
|
691
|
+
SmartLibrary.really_delete_deleted_libraries
|
692
|
+
end
|
693
|
+
|
694
|
+
def really_save_all_books
|
695
|
+
all_regular_libraries.each do |library|
|
696
|
+
library.each { |book| library.save(book, true) }
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
#######
|
701
|
+
private
|
702
|
+
#######
|
703
|
+
|
704
|
+
def initialize
|
705
|
+
@all_libraries = []
|
706
|
+
@deleted_books = []
|
707
|
+
end
|
708
|
+
|
709
|
+
def notify(action, library)
|
710
|
+
changed
|
711
|
+
notify_observers(self, action, library)
|
712
|
+
end
|
713
|
+
end
|
714
|
+
end
|