alexandria-book-collection-manager 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.reek +3 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +20 -23
- data/.simplecov +3 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -2
- data/INSTALL.rdoc +0 -8
- data/README.md +72 -0
- data/Rakefile +4 -2
- data/TODO.md +26 -0
- data/alexandria-book-collection-manager.gemspec +6 -3
- data/bin/alexandria +1 -6
- data/dogtail/basic_run_test.py +9 -0
- data/lib/alexandria/book_providers.rb +2 -6
- data/lib/alexandria/book_providers/proxis.rb +1 -1
- data/lib/alexandria/book_providers/web.rb +1 -1
- data/lib/alexandria/book_providers/z3950.rb +4 -4
- data/lib/alexandria/export_library.rb +20 -27
- data/lib/alexandria/import_library.rb +7 -5
- data/lib/alexandria/models/book.rb +11 -1
- data/lib/alexandria/models/library.rb +4 -17
- data/lib/alexandria/preferences.rb +5 -5
- data/lib/alexandria/smart_library.rb +2 -8
- data/lib/alexandria/ui/callbacks.rb +1 -1
- data/lib/alexandria/ui/columns.rb +9 -0
- data/lib/alexandria/ui/completion_models.rb +1 -29
- data/lib/alexandria/ui/dialogs/acquire_dialog.rb +2 -3
- data/lib/alexandria/ui/dialogs/book_properties_dialog_base.rb +1 -2
- data/lib/alexandria/ui/dialogs/new_book_dialog_manual.rb +3 -2
- data/lib/alexandria/ui/iconview.rb +1 -6
- data/lib/alexandria/ui/libraries_combo.rb +1 -0
- data/lib/alexandria/ui/listview.rb +5 -10
- data/lib/alexandria/ui/main_app.rb +0 -1
- data/lib/alexandria/ui/sidepane.rb +1 -1
- data/lib/alexandria/ui/sound.rb +3 -3
- data/lib/alexandria/ui/ui_manager.rb +5 -13
- data/lib/alexandria/version.rb +2 -2
- data/spec/alexandria/book_providers_spec.rb +0 -17
- data/spec/alexandria/book_spec.rb +23 -0
- data/spec/alexandria/library_spec.rb +26 -0
- data/spec/alexandria/smart_library_spec.rb +11 -1
- data/spec/alexandria/ui/main_app_spec.rb +0 -11
- data/spec/spec_helper.rb +0 -1
- data/tasks/dogtail.rake +4 -0
- data/tasks/spec.rake +3 -3
- metadata +58 -12
- data/TODO +0 -24
- data/spec/alexandria/ui/listview_spec.rb +0 -28
- data/tasks/rdoc.rake +0 -6
@@ -22,13 +22,8 @@
|
|
22
22
|
# iPod Notes support added 20 January 2008 by Tim Malone
|
23
23
|
# require 'cgi'
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
require 'image_size'
|
28
|
-
rescue LoadError
|
29
|
-
$IMAGE_SIZE_LOADED = false
|
30
|
-
puts "Can't load image_size, hence exported libraries are not optimized" if $DEBUG
|
31
|
-
end
|
25
|
+
require 'csv'
|
26
|
+
require 'image_size'
|
32
27
|
|
33
28
|
module Alexandria
|
34
29
|
class LibrarySortOrder
|
@@ -55,8 +50,7 @@ module Alexandria
|
|
55
50
|
end
|
56
51
|
|
57
52
|
class Unsorted < LibrarySortOrder
|
58
|
-
def initialize
|
59
|
-
end
|
53
|
+
def initialize; end
|
60
54
|
|
61
55
|
def sort(library)
|
62
56
|
library
|
@@ -180,8 +174,9 @@ module Alexandria
|
|
180
174
|
FileUtils.mkdir(filename) unless File.exist?(filename)
|
181
175
|
Dir.chdir(filename) do
|
182
176
|
copy_covers('pixmaps')
|
183
|
-
|
184
|
-
|
177
|
+
if theme.has_pixmaps?
|
178
|
+
FileUtils.cp_r(theme.pixmaps_directory, 'pixmaps')
|
179
|
+
end
|
185
180
|
FileUtils.cp(theme.css_file, '.')
|
186
181
|
File.open('index.html', 'w') do |io|
|
187
182
|
io << to_xhtml(File.basename(theme.css_file))
|
@@ -228,10 +223,15 @@ module Alexandria
|
|
228
223
|
end
|
229
224
|
|
230
225
|
def export_as_csv_list(filename)
|
231
|
-
|
232
|
-
|
226
|
+
CSV.open(filename, 'w', col_sep: ';') do |csv|
|
227
|
+
csv << ['Title', 'Authors', 'Publisher', 'Edition', 'ISBN', 'Year Published',
|
228
|
+
"Rating(#{Book::DEFAULT_RATING} to #{Book::MAX_RATING_STARS})", 'Notes',
|
229
|
+
'Want?', 'Read?', 'Own?', 'Tags']
|
233
230
|
each do |book|
|
234
|
-
|
231
|
+
csv << [book.title, book.authors.join(', '), book.publisher, book.edition, book.isbn,
|
232
|
+
book.publishing_year, book.rating, book.notes,
|
233
|
+
(book.want ? '1' : '0'), (book.redd ? '1' : '0'), (book.own ? '1' : '0'),
|
234
|
+
(book.tags ? book.tags.join(', ') : '')]
|
235
235
|
end
|
236
236
|
end
|
237
237
|
end
|
@@ -352,15 +352,10 @@ module Alexandria
|
|
352
352
|
entry.add_element('cover').text = final_cover(book)
|
353
353
|
image = images.add_element('image')
|
354
354
|
image.add_attribute('id', final_cover(book))
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
image.add_attribute('format', image_s.get_type)
|
360
|
-
else
|
361
|
-
image.add_attribute('format',
|
362
|
-
Library.jpeg?(cover(book)) ? 'JPEG' : 'GIF')
|
363
|
-
end
|
355
|
+
image_s = ImageSize.new(IO.read(cover(book)))
|
356
|
+
image.add_attribute('height', image_s.get_height.to_s)
|
357
|
+
image.add_attribute('width', image_s.get_width.to_s)
|
358
|
+
image.add_attribute('format', image_s.get_type)
|
364
359
|
end
|
365
360
|
end
|
366
361
|
doc
|
@@ -409,12 +404,10 @@ EOS
|
|
409
404
|
src="#{File.join('pixmaps', final_cover(book))}"
|
410
405
|
alt="Cover file for '#{xhtml_escape(book.title)}'"
|
411
406
|
EOS
|
412
|
-
|
413
|
-
|
414
|
-
xhtml << <<EOS
|
407
|
+
image_s = ImageSize.new(IO.read(cover(book)))
|
408
|
+
xhtml << <<EOS
|
415
409
|
height="#{image_s.get_height}" width="#{image_s.get_width}"
|
416
410
|
EOS
|
417
|
-
end
|
418
411
|
xhtml << <<EOS
|
419
412
|
/>
|
420
413
|
EOS
|
@@ -141,7 +141,7 @@ module Alexandria
|
|
141
141
|
end
|
142
142
|
puts cover
|
143
143
|
book = Book.new(*book_elements)
|
144
|
-
if elements['rating'] &&
|
144
|
+
if elements['rating'] && Book::VALID_RATINGS.member?(elements['rating'].text.to_i)
|
145
145
|
book.rating = elements['rating'].text.to_i
|
146
146
|
end
|
147
147
|
book.notes = neaten(elements['comments'].text) if elements['comments']
|
@@ -278,8 +278,9 @@ module Alexandria
|
|
278
278
|
# (on_error_cb and on_error_cb.call(e.message))
|
279
279
|
end
|
280
280
|
|
281
|
-
on_iterate_cb
|
282
|
-
|
281
|
+
if on_iterate_cb
|
282
|
+
on_iterate_cb.call(current_iteration += 1, max_iterations)
|
283
|
+
end
|
283
284
|
end
|
284
285
|
puts "Bad Isbn list: #{bad_isbns.inspect}" if bad_isbns
|
285
286
|
library = load(name)
|
@@ -290,8 +291,9 @@ module Alexandria
|
|
290
291
|
puts "Saving #{book.isbn}..." if $DEBUG
|
291
292
|
library << book
|
292
293
|
library.save(book)
|
293
|
-
on_iterate_cb
|
294
|
-
|
294
|
+
if on_iterate_cb
|
295
|
+
on_iterate_cb.call(current_iteration += 1, max_iterations)
|
296
|
+
end
|
295
297
|
end
|
296
298
|
[library, bad_isbns, failed_lookup_isbns]
|
297
299
|
end
|
@@ -18,10 +18,14 @@
|
|
18
18
|
module Alexandria
|
19
19
|
class Book
|
20
20
|
attr_accessor :title, :authors, :isbn, :publisher, :publishing_year,
|
21
|
-
:edition, :
|
21
|
+
:edition, :notes, :loaned, :loaned_since,
|
22
22
|
:loaned_to, :saved_ident, :redd, :redd_when, :own, :want, :tags, :version, :library
|
23
23
|
|
24
|
+
attr_reader :rating
|
25
|
+
|
24
26
|
DEFAULT_RATING = 0
|
27
|
+
MAX_RATING_STARS = 5
|
28
|
+
VALID_RATINGS = (DEFAULT_RATING..MAX_RATING_STARS)
|
25
29
|
|
26
30
|
def initialize(title, authors, isbn, publisher, publishing_year,
|
27
31
|
edition)
|
@@ -38,6 +42,7 @@ module Alexandria
|
|
38
42
|
@own = true
|
39
43
|
@want = true
|
40
44
|
@tags = []
|
45
|
+
@rating = DEFAULT_RATING
|
41
46
|
# Need to implement bulk save function to update this
|
42
47
|
@version = Alexandria::DATA_VERSION
|
43
48
|
end
|
@@ -47,6 +52,11 @@ module Alexandria
|
|
47
52
|
@isbn || @title.hash.to_s
|
48
53
|
end
|
49
54
|
|
55
|
+
def rating=(rating)
|
56
|
+
raise ArgumentError unless VALID_RATINGS.include? rating
|
57
|
+
@rating = rating
|
58
|
+
end
|
59
|
+
|
50
60
|
def loaned?
|
51
61
|
loaned || false
|
52
62
|
end
|
@@ -25,12 +25,6 @@ require 'open-uri'
|
|
25
25
|
require 'observer'
|
26
26
|
require 'singleton'
|
27
27
|
|
28
|
-
class Array
|
29
|
-
def sum
|
30
|
-
reduce(0) { |a, b| a + b }
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
28
|
module Alexandria
|
35
29
|
class Library < Array
|
36
30
|
include Logging
|
@@ -270,9 +264,6 @@ module Alexandria
|
|
270
264
|
end
|
271
265
|
|
272
266
|
class NoISBNError < StandardError
|
273
|
-
def initialize(msg)
|
274
|
-
super(msg)
|
275
|
-
end
|
276
267
|
end
|
277
268
|
|
278
269
|
class InvalidISBNError < StandardError
|
@@ -308,8 +299,8 @@ module Alexandria
|
|
308
299
|
end
|
309
300
|
|
310
301
|
def self.ean_checksum(numbers)
|
311
|
-
|
312
|
-
|
302
|
+
-(numbers.values_at(1, 3, 5, 7, 9, 11).reduce(:+) * 3 +
|
303
|
+
numbers.values_at(0, 2, 4, 6, 8, 10).reduce(:+)) % 10
|
313
304
|
end
|
314
305
|
|
315
306
|
def self.valid_ean?(ean)
|
@@ -323,8 +314,8 @@ module Alexandria
|
|
323
314
|
end
|
324
315
|
|
325
316
|
def self.upc_checksum(numbers)
|
326
|
-
|
327
|
-
|
317
|
+
-(numbers.values_at(0, 2, 4, 6, 8, 10).reduce(:+) * 3 +
|
318
|
+
numbers.values_at(1, 3, 5, 7, 9).reduce(:+)) % 10
|
328
319
|
end
|
329
320
|
|
330
321
|
def self.valid_upc?(upc)
|
@@ -571,8 +562,6 @@ module Alexandria
|
|
571
562
|
something
|
572
563
|
when Integer
|
573
564
|
something
|
574
|
-
when Integer
|
575
|
-
something
|
576
565
|
else
|
577
566
|
raise "#{something} is a #{something.class}"
|
578
567
|
end
|
@@ -587,8 +576,6 @@ module Alexandria
|
|
587
576
|
something
|
588
577
|
when Integer
|
589
578
|
something
|
590
|
-
when Integer
|
591
|
-
something
|
592
579
|
else
|
593
580
|
raise "#{something} is #{something.class}"
|
594
581
|
end
|
@@ -257,13 +257,13 @@ module Alexandria
|
|
257
257
|
# range of values used by Alexandria.
|
258
258
|
def discriminate(value)
|
259
259
|
if value == 'true' # bool
|
260
|
-
|
260
|
+
true
|
261
261
|
elsif value == 'false' # bool
|
262
|
-
|
262
|
+
false
|
263
263
|
elsif value =~ /^[0-9]+$/ # int
|
264
|
-
|
264
|
+
value.to_i
|
265
265
|
elsif value =~ /^\[(.*)\]$/ # list (assume of type String)
|
266
|
-
|
266
|
+
Regexp.last_match[1].split(',')
|
267
267
|
elsif value =~ /^\((.*)\)$/ # pair (assume of type int)
|
268
268
|
begin
|
269
269
|
pair = Regexp.last_match[1].split(',')
|
@@ -272,7 +272,7 @@ module Alexandria
|
|
272
272
|
return [0, 0]
|
273
273
|
end
|
274
274
|
else
|
275
|
-
|
275
|
+
value # string
|
276
276
|
end
|
277
277
|
end
|
278
278
|
end
|
@@ -37,10 +37,9 @@ module Alexandria
|
|
37
37
|
def initialize(name, rules, predicate_operator_rule)
|
38
38
|
super()
|
39
39
|
raise if name.nil? || rules.nil? || predicate_operator_rule.nil?
|
40
|
-
@name = name
|
40
|
+
@name = name.dup.force_encoding('UTF-8')
|
41
41
|
@rules = rules
|
42
42
|
@predicate_operator_rule = predicate_operator_rule
|
43
|
-
save
|
44
43
|
libraries = Libraries.instance
|
45
44
|
libraries.add_observer(self)
|
46
45
|
self.libraries = libraries.all_regular_libraries
|
@@ -90,7 +89,7 @@ module Alexandria
|
|
90
89
|
# Favorite books.
|
91
90
|
rule = Rule.new(operands.find { |x| x.book_selector == :rating },
|
92
91
|
Rule::Operators::IS,
|
93
|
-
|
92
|
+
Book::MAX_RATING_STARS.to_s)
|
94
93
|
a << new(_('Favorite'), [rule], ALL_RULES)
|
95
94
|
|
96
95
|
# Loaned books.
|
@@ -159,11 +158,6 @@ module Alexandria
|
|
159
158
|
end
|
160
159
|
|
161
160
|
def refilter
|
162
|
-
raise 'need libraries' if @libraries.nil?
|
163
|
-
raise 'no libraries' if @libraries.empty?
|
164
|
-
raise 'need predicate operator' if @predicate_operator_rule.nil?
|
165
|
-
raise 'need rule' if @rules.nil? || @rules.empty?
|
166
|
-
|
167
161
|
filters = @rules.map(&:filter_proc)
|
168
162
|
selector = @predicate_operator_rule == ALL_RULES ? :all? : :any?
|
169
163
|
|
@@ -44,6 +44,7 @@ module Alexandria
|
|
44
44
|
min = 2
|
45
45
|
completion.signal_connect('match-selected') do |c, model, iter|
|
46
46
|
cur_text = c.entry.text
|
47
|
+
# TODO: Replace with iter[0] if possible
|
47
48
|
new_tag = model.get_value(iter, 0)
|
48
49
|
cur_text_split = cur_text.split(',')
|
49
50
|
cur_text_split.delete_at(-1)
|
@@ -83,34 +84,6 @@ end
|
|
83
84
|
|
84
85
|
Gtk::Entry.prepend Alexandria::EntryOverrides
|
85
86
|
|
86
|
-
begin
|
87
|
-
require 'revolution'
|
88
|
-
|
89
|
-
EVOLUTION_CONTACTS =
|
90
|
-
Revolution::Revolution.new.get_all_contacts.map do |contact|
|
91
|
-
first = contact.first_name
|
92
|
-
last = contact.last_name
|
93
|
-
|
94
|
-
if first
|
95
|
-
first.strip!
|
96
|
-
first = nil if first.empty?
|
97
|
-
end
|
98
|
-
|
99
|
-
if last
|
100
|
-
last.strip!
|
101
|
-
last = nil if last.empty?
|
102
|
-
end
|
103
|
-
|
104
|
-
first && last ? first + ' ' + last : first ? first : last
|
105
|
-
end
|
106
|
-
rescue LoadError => e
|
107
|
-
Alexandria.log.debug { 'Could not find optional ruby-revolution; Evolution contacts will not be loaded' }
|
108
|
-
EVOLUTION_CONTACTS = [].freeze
|
109
|
-
rescue => e
|
110
|
-
Alexandria.log.warn { e.message }
|
111
|
-
EVOLUTION_CONTACTS = [].freeze
|
112
|
-
end
|
113
|
-
|
114
87
|
module Alexandria
|
115
88
|
module UI
|
116
89
|
class CompletionModels
|
@@ -207,7 +180,6 @@ module Alexandria
|
|
207
180
|
end
|
208
181
|
end
|
209
182
|
|
210
|
-
borrowers.concat(EVOLUTION_CONTACTS)
|
211
183
|
borrowers.uniq!
|
212
184
|
|
213
185
|
tags.uniq!
|
@@ -236,8 +236,7 @@ module Alexandria
|
|
236
236
|
@acquire_dialog.destroy
|
237
237
|
end
|
238
238
|
|
239
|
-
def on_help
|
240
|
-
end
|
239
|
+
def on_help; end
|
241
240
|
|
242
241
|
def read_barcode_scan
|
243
242
|
@animation.start
|
@@ -535,7 +534,7 @@ module Alexandria
|
|
535
534
|
def play_sound(effect)
|
536
535
|
if effect == 'scanning'
|
537
536
|
puts "Effect: #{effect}, playing: #{@prefs.play_scanning_sound}" if $DEBUG
|
538
|
-
return unless
|
537
|
+
return unless @prefs.play_scanning_sound
|
539
538
|
@sound_players['scanning'].play('scanning')
|
540
539
|
else
|
541
540
|
puts "Effect: #{effect}, playing: #{@prefs.play_scan_sound}" if $DEBUG
|
@@ -84,8 +84,9 @@ module Alexandria
|
|
84
84
|
ary = @library.select { |book|
|
85
85
|
book.ident == @entry_isbn.text
|
86
86
|
}
|
87
|
-
|
88
|
-
|
87
|
+
unless ary.empty?
|
88
|
+
raise AddError, _('The EAN/ISBN you provided is already used in this library.')
|
89
|
+
end
|
89
90
|
isbn = begin
|
90
91
|
Library.canonicalise_isbn(@entry_isbn.text)
|
91
92
|
rescue Alexandria::Library::InvalidISBNError
|
@@ -17,16 +17,11 @@
|
|
17
17
|
# write to the Free Software Foundation, Inc., 51 Franklin Street,
|
18
18
|
# Fifth Floor, Boston, MA 02110-1301 USA.
|
19
19
|
|
20
|
+
require 'alexandria/ui/columns'
|
20
21
|
require 'alexandria/ui/iconview_tooltips'
|
21
22
|
|
22
23
|
module Alexandria
|
23
24
|
module UI
|
24
|
-
module Columns
|
25
|
-
COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
|
26
|
-
ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
|
27
|
-
NOTES, REDD, OWN, WANT, TAGS = (0..16).to_a
|
28
|
-
end
|
29
|
-
|
30
25
|
class IconViewManager
|
31
26
|
ICON_WIDTH = 60
|
32
27
|
include Logging
|
@@ -18,6 +18,8 @@
|
|
18
18
|
# write to the Free Software Foundation, Inc., 51 Franklin Street,
|
19
19
|
# Fifth Floor, Boston, MA 02110-1301 USA.
|
20
20
|
|
21
|
+
require 'alexandria/ui/columns'
|
22
|
+
|
21
23
|
module Alexandria
|
22
24
|
module UI
|
23
25
|
include Logging
|
@@ -29,13 +31,6 @@ module Alexandria
|
|
29
31
|
include DragAndDropable
|
30
32
|
BOOKS_TARGET_TABLE = [['ALEXANDRIA_BOOKS', :same_app, 0]].freeze
|
31
33
|
|
32
|
-
MAX_RATING_STARS = 5
|
33
|
-
module Columns
|
34
|
-
COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
|
35
|
-
ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
|
36
|
-
NOTES, REDD, OWN, WANT, TAGS, LOANED_TO = (0..17).to_a
|
37
|
-
end
|
38
|
-
|
39
34
|
def initialize(_listview, parent)
|
40
35
|
@parent = parent
|
41
36
|
@prefs = @parent.prefs
|
@@ -127,14 +122,14 @@ module Alexandria
|
|
127
122
|
log.debug { 'Create listview column for %s...' % title }
|
128
123
|
column = Gtk::TreeViewColumn.new(title)
|
129
124
|
column.sizing = :fixed
|
130
|
-
width = (Icons::STAR_SET.width + 1) * MAX_RATING_STARS
|
125
|
+
width = (Icons::STAR_SET.width + 1) * Book::MAX_RATING_STARS
|
131
126
|
column.fixed_width = column.min_width = column.max_width = width
|
132
|
-
MAX_RATING_STARS.times do |i|
|
127
|
+
Book::MAX_RATING_STARS.times do |i|
|
133
128
|
renderer = Gtk::CellRendererPixbuf.new
|
134
129
|
renderer.xalign = 0.0
|
135
130
|
column.pack_start(renderer, false)
|
136
131
|
column.set_cell_data_func(renderer) do |_tree_column, cell, _tree_model, iter|
|
137
|
-
rating = (iter[Columns::RATING] - MAX_RATING_STARS).abs
|
132
|
+
rating = (iter[Columns::RATING] - Book::MAX_RATING_STARS).abs
|
138
133
|
cell.pixbuf = rating >= i.succ ?
|
139
134
|
Icons::STAR_SET : Icons::STAR_UNSET
|
140
135
|
end
|