Class: Alexandria::Library

Inherits:
Array
  • Object
show all
Extended by:
GetText
Includes:
ImageFetcher, Logging, GetText, Observable
Defined in:
lib/alexandria/import_library.rb,
lib/alexandria/models/library.rb,
lib/alexandria/import_library_csv.rb,
lib/alexandria/ui/init.rb

Constant Summary collapse

DEFAULT_DIR =
File.join(Dir.home, ".alexandria")
EXT =
{ book: ".yaml", cover: ".cover" }.freeze
AMERICAN_UPC_LOOKUP =
{
  "014794" => "08041", "018926" => "0445", "02778" => "0449",
  "037145" => "0812", "042799" => "0785",  "043144" => "0688",
  "044903" => "0312", "045863" => "0517", "046594" => "0064",
  "047132" => "0152", "051487" => "08167", "051488" => "0140",
  "060771" => "0002", "065373" => "0373", "070992" => "0523",
  "070993" => "0446", "070999" => "0345", "071001" => "0380",
  "071009" => "0440", "071125" => "088677", "071136" => "0451",
  "071149" => "0451", "071152" => "0515", "071162" => "0451",
  "071268" => "08217", "071831" => "0425", "071842" => "08439",
  "072742" => "0441", "076714" => "0671", "076783" => "0553",
  "076814" => "0449", "078021" => "0872", "079808" => "0394",
  "090129" => "0679", "099455" => "0061", "099769" => "0451"
}.freeze
@@deleted_libraries =
[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ImageFetcher

#fetch_image

Methods included from Logging

included, #log

Instance Attribute Details

#deleted_booksObject

Returns the value of attribute deleted_books.



21
22
23
# File 'lib/alexandria/models/library.rb', line 21

def deleted_books
  @deleted_books
end

#nameObject

Returns the value of attribute name.



20
21
22
# File 'lib/alexandria/models/library.rb', line 20

def name
  @name
end

#ruined_booksObject

Returns the value of attribute ruined_books.



21
22
23
# File 'lib/alexandria/models/library.rb', line 21

def ruined_books
  @ruined_books
end

#updatingObject

Returns the value of attribute updating.



21
22
23
# File 'lib/alexandria/models/library.rb', line 21

def updating
  @updating
end

Class Method Details

.canonicalise_ean(code) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/alexandria/models/library.rb', line 144

def self.canonicalise_ean(code)
  code = code.to_s.delete("- ")
  if valid_ean?(code)
    code
  elsif valid_isbn?(code)
    code = "978" + code[0..8]
    code + String(ean_checksum(extract_numbers(code)))
  elsif valid_upc?(code)
    isbn10 = canonicalise_isbn
    code = "978" + isbn10[0..8]
    code + String(ean_checksum(extract_numbers(code)))
  end
end

.canonicalise_isbn(isbn) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/alexandria/models/library.rb', line 158

def self.canonicalise_isbn(isbn)
  numbers = extract_numbers(isbn)
  return isbn if valid_ean?(isbn) && (numbers[0..2] != [9, 7, 8])

  canonical = if valid_ean?(isbn)
                # Looks like an EAN number -- extract the intersting part and
                # calculate a checksum. It would be nice if we could validate
                # the EAN number somehow.
                numbers[3..11] + [isbn_checksum(numbers[3..11])]
              elsif valid_upc?(isbn)
                # Seems to be a valid UPC number.
                prefix = upc_convert(numbers[0..5])
                isbn_sans_chcksm = prefix + numbers[(8 + prefix.length)..17]
                isbn_sans_chcksm + [isbn_checksum(isbn_sans_chcksm)]
              elsif valid_isbn?(isbn)
                # Seems to be a valid ISBN number.
                numbers[0..-2] + [isbn_checksum(numbers[0..-2])]
              end

  return unless canonical

  canonical.map(&:to_s).join
end

.deleted_librariesObject



255
256
257
# File 'lib/alexandria/models/library.rb', line 255

def self.deleted_libraries
  @@deleted_libraries
end

.ean_checksum(numbers) ⇒ Object



100
101
102
103
# File 'lib/alexandria/models/library.rb', line 100

def self.ean_checksum(numbers)
  -(numbers.values_at(1, 3, 5, 7, 9, 11).sum * 3 +
    numbers.values_at(0, 2, 4, 6, 8, 10).sum) % 10
end

.extract_numbers(entry) ⇒ Object



76
77
78
79
80
81
82
83
84
85
# File 'lib/alexandria/models/library.rb', line 76

def self.extract_numbers(entry)
  return [] if entry.nil? || entry.empty?

  normalized = entry.delete("- ").upcase
  return [] unless /\A[\dX]*\Z/.match?(normalized)

  normalized.chars.map do |char|
    char == "X" ? 10 : char.to_i
  end
end

.generate_new_name(existing_libraries, from_base = _("Untitled")) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/alexandria/models/library.rb', line 41

def self.generate_new_name(existing_libraries,
                           from_base = _("Untitled"))
  i = 1
  name = nil
  all_libraries = existing_libraries + @@deleted_libraries
  loop do
    name = i == 1 ? from_base : from_base + " #{i}"
    break unless all_libraries.find { |x| x.name == name }

    i += 1
  end
  name
end

.identify_csv_type(header) ⇒ Object

LibraryThing has 15 fields (Apr 2010), Goodreads has 29 we shall guess that “PUBLICATION INFO” implies LibraryThing and “Year Published” implies Goodreads



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/alexandria/import_library_csv.rb', line 182

def self.identify_csv_type(header)
  is_librarything = false
  is_goodreads = false
  header.each do |head|
    case head
    when "'PUBLICATION INFO'"
      is_librarything = true
      break
    when "Year Published"
      is_goodreads = true
      break
    end
  end

  return LibraryThingCSVImport.new(header) if is_librarything
  return GoodreadsCSVImport.new(header) if is_goodreads

  raise _("Not Recognized") unless is_librarything || is_goodreads
end

.import_as_csv_file(name, filename, on_iterate_cb, _on_error_cb) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/alexandria/import_library.rb', line 139

def self.import_as_csv_file(name, filename, on_iterate_cb, _on_error_cb)
  require "alexandria/import_library_csv"
  books_and_covers = []
  line_count = File.readlines(filename).reduce(0) { |count, _line| count + 1 }

  import_count = 0
  max_import = line_count - 1

  reader = CSV.open(filename, "r")
  # Goodreads & LibraryThing now use csv header lines
  header = reader.shift
  importer = identify_csv_type(header)
  failed_once = false
  begin
    reader.each do |row|
      book = importer.row_to_book(row)
      cover = nil
      if book.isbn
        # if we can search by ISBN, try to grab the cover
        begin
          dl_book, dl_cover = BookProviders.isbn_search(book.isbn)
          if dl_book.authors.size > book.authors.size
            # LibraryThing only supports a single author, so
            # attempt to include more author information if it's
            # available
            book.authors = dl_book.authors
          end
          book.edition = dl_book.edition unless book.edition
          cover = dl_cover
        rescue StandardError
          log.debug { "Failed to get cover for #{book.title} #{book.isbn}" }
        end
      end

      books_and_covers << [book, cover]
      import_count += 1
      on_iterate_cb&.call(import_count, max_import)
    end
  rescue CSV::IllegalFormatError
    unless failed_once
      failed_once = true

      # probably Goodreads' wonky ISBN fields ,,="043432432X",
      # this is a hack to fix up such files
      data = File.read(filename)
      data.gsub!(/,="/, ',"')
      csv_fixed = Tempfile.new("alexandria_import_csv_fixed_")
      csv_fixed.write(data)
      csv_fixed.close

      reader = CSV.open(csv_fixed.path, "r")
      header = reader.shift
      importer = identify_csv_type(header)

      retry
    end
  end

  # TODO: Pass in library store as an argument
  library = LibraryCollection.instance.library_store.load_library name

  books_and_covers.each do |book, cover_uri|
    log.debug { "Saving #{book.isbn} cover" }
    library.save_cover(book, cover_uri) unless cover_uri.nil?
    log.debug { "Saving #{book.isbn}" }
    library << book
    library.save(book)
  end
  [library, []]
end

.import_as_isbn_list(name, filename, on_iterate_cb, on_error_cb) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/alexandria/import_library.rb', line 210

def self.import_as_isbn_list(name, filename, on_iterate_cb,
                             on_error_cb)
  log.debug { "Starting import_as_isbn_list... " }
  isbn_list = File.readlines(filename).map do |line|
    log.debug { "Trying line #{line}" }
    [line.chomp, canonicalise_isbn(line.chomp)] unless line == "\n"
  end
  log.debug { "Isbn list: #{isbn_list.inspect}" }
  isbn_list.compact!
  return nil if isbn_list.empty?

  max_iterations = isbn_list.length * 2
  current_iteration = 1
  books = []
  bad_isbns = []
  failed_lookup_isbns = []
  isbn_list.each do |isbn|
    begin
      if isbn[1]
        books << BookProviders.isbn_search(isbn[1])
      else
        bad_isbns << isbn[0]
      end
    rescue BookProviders::SearchEmptyError => ex
      log.debug { ex.message }
      failed_lookup_isbns << isbn[1]
      log.debug { "NOTE : ignoring on_error_cb #{on_error_cb}" }
      # return nil unless
      #  (on_error_cb and on_error_cb.call(e.message))
    end

    on_iterate_cb&.call(current_iteration += 1, max_iterations)
  end
  log.debug { "Bad Isbn list: #{bad_isbns.inspect}" } if bad_isbns

  # TODO: Pass in library store as an argument
  library = LibraryCollection.instance.library_store.load_library name

  log.debug { "Going with these #{books.length} books: #{books.inspect}" }
  books.each do |book, cover_uri|
    log.debug { "Saving #{book.isbn} cover..." }
    library.save_cover(book, cover_uri) unless cover_uri.nil?
    log.debug { "Saving #{book.isbn}..." }
    library << book
    library.save(book)
    on_iterate_cb&.call(current_iteration += 1, max_iterations)
  end
  [library, bad_isbns, failed_lookup_isbns]
end

.import_as_tellico_xml_archive(name, filename, on_iterate_cb, _on_error_cb) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/alexandria/import_library.rb', line 69

def self.import_as_tellico_xml_archive(name, filename,
                                       on_iterate_cb, _on_error_cb)
  log.debug { "Starting import_as_tellico_xml_archive... " }
  return nil unless system("unzip -qqt \"#{filename}\"")

  tmpdir = File.join(Dir.tmpdir, "tellico_export")
  FileUtils.rm_rf(tmpdir)
  Dir.mkdir(tmpdir)
  Dir.chdir(tmpdir) do
    system("unzip -qq \"#{filename}\"")
    file = File.exist?("bookcase.xml") ? "bookcase.xml" : "tellico.xml"
    xml = REXML::Document.new(File.open(file))
    raise unless ["bookcase", "tellico"].include? xml.root.name
    # FIXME: handle multiple collections
    raise unless xml.root.elements.size == 1

    collection = xml.root.elements[1]
    raise unless collection.name == "collection"

    type = collection.attribute("type").value.to_i
    raise unless (type == 2) || (type == 5)

    content = []
    entries = collection.elements.to_a("entry")
    (total = entries.size).times do |n|
      entry = entries[n]
      elements = entry.elements
      # Feed an array in here, tomorrow.
      keys = ["isbn", "publisher", "pub_year", "binding"]

      book_elements = [neaten(elements["title"].text)]
      book_elements += if elements["authors"].nil?
                         [[]]
                       else
                         [elements["authors"].elements.to_a.map \
                                           { |x| neaten(x.text) }]
                       end
      book_elements += keys.map do |key|
        neaten(elements[key].text) if elements[key]
      end
      book_elements[2] = Library.canonicalise_ean(book_elements[2])
      # publishing_year
      book_elements[4] = book_elements[4].to_i unless book_elements[4].nil?
      log.debug { book_elements.inspect }
      cover = (neaten(elements["cover"].text) if elements["cover"])
      log.debug { cover }
      book = Book.new(*book_elements)
      if elements["rating"]
        rating = elements["rating"].text.to_i
        book.rating = rating if Book::VALID_RATINGS.member? rating
      end
      book.notes = neaten(elements["comments"].text) if elements["comments"]
      content << [book, cover]
      on_iterate_cb&.call(n + 1, total)
    end

    # TODO: Pass in library store as an argument
    library = LibraryCollection.instance.library_store.load_library name
    content.each do |book, cover|
      library.save_cover(book, File.join(Dir.pwd, "images", cover)) unless cover.nil?
      library << book
      library.save(book)
    end
    return [library, []]
  rescue StandardError => ex
    log.info { ex.message }
    return nil
  end
end

.import_autodetect(*args) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/alexandria/import_library.rb', line 53

def self.import_autodetect(*args)
  log.debug { args.inspect }
  filename = args[1]
  log.debug { "Filename is #{filename} and ext is #{filename[-4..]}" }
  log.debug { "Beginning import: #{args[0]}, #{args[1]}" }
  if filename[-4..] == ".txt"
    import_as_isbn_list(*args)
  elsif [".tc", ".bc"].include? filename[-3..]
    import_as_tellico_xml_archive(*args)
  elsif [".csv"].include? filename[-4..]
    import_as_csv_file(*args)
  else
    raise _("Unsupported type")
  end
end

.isbn_checksum(numbers) ⇒ Object



87
88
89
90
91
92
93
# File 'lib/alexandria/models/library.rb', line 87

def self.isbn_checksum(numbers)
  sum = (0...numbers.length).reduce(0) do |accumulator, i|
    accumulator + numbers[i] * (i + 1)
  end % 11

  sum == 10 ? "X" : sum
end

.jpeg?(file) ⇒ Boolean

Returns:

  • (Boolean)


392
393
394
# File 'lib/alexandria/models/library.rb', line 392

def self.jpeg?(file)
  File.read(file, 10)[6..9] == "JFIF"
end

.move(source_library, dest_library, *books) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/alexandria/models/library.rb', line 55

def self.move(source_library, dest_library, *books)
  dest = dest_library.path
  books.each do |book|
    FileUtils.mv(source_library.yaml(book), dest)
    if File.exist?(source_library.cover(book))
      FileUtils.mv(source_library.cover(book), dest)
    end

    source_library.changed
    source_library.old_delete(book)
    source_library.notify_observers(source_library,
                                    BOOK_REMOVED,
                                    book)

    dest_library.changed
    dest_library.delete_if { |book2| book2.ident == book.ident }
    dest_library << book
    dest_library.notify_observers(dest_library, BOOK_ADDED, book)
  end
end

.neaten(str) ⇒ Object



260
261
262
263
264
265
266
# File 'lib/alexandria/import_library.rb', line 260

def self.neaten(str)
  if str
    str.strip
  else
    str
  end
end

.really_delete_deleted_librariesObject



259
260
261
262
263
# File 'lib/alexandria/models/library.rb', line 259

def self.really_delete_deleted_libraries
  @@deleted_libraries.each do |library|
    FileUtils.rm_rf(library.path)
  end
end

.upc_checksum(numbers) ⇒ Object



113
114
115
116
# File 'lib/alexandria/models/library.rb', line 113

def self.upc_checksum(numbers)
  -(numbers.values_at(0, 2, 4, 6, 8, 10).sum * 3 +
    numbers.values_at(1, 3, 5, 7, 9).sum) % 10
end

.upc_convert(upc) ⇒ Object



139
140
141
142
# File 'lib/alexandria/models/library.rb', line 139

def self.upc_convert(upc)
  test_upc = upc.map(&:to_s).join
  extract_numbers(AMERICAN_UPC_LOOKUP[test_upc])
end

.valid_ean?(ean) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
108
109
110
111
# File 'lib/alexandria/models/library.rb', line 105

def self.valid_ean?(ean)
  numbers = extract_numbers(ean)
  ((numbers.length == 13) &&
   (ean_checksum(numbers[0..11]) == numbers[12])) ||
    ((numbers.length == 18) &&
     (ean_checksum(numbers[0..11]) == numbers[12]))
end

.valid_isbn?(isbn) ⇒ Boolean

Returns:

  • (Boolean)


95
96
97
98
# File 'lib/alexandria/models/library.rb', line 95

def self.valid_isbn?(isbn)
  numbers = extract_numbers(isbn)
  (numbers.length == 10) && isbn_checksum(numbers).zero?
end

.valid_upc?(upc) ⇒ Boolean

Returns:

  • (Boolean)


118
119
120
121
122
# File 'lib/alexandria/models/library.rb', line 118

def self.valid_upc?(upc)
  numbers = extract_numbers(upc)
  ((numbers.length == 17) &&
   (upc_checksum(numbers[0..10]) == numbers[11]))
end

Instance Method Details

#==(other) ⇒ Object



377
378
379
# File 'lib/alexandria/models/library.rb', line 377

def ==(other)
  other.is_a?(self.class) && other.name == name
end

#action_nameObject



57
58
59
# File 'lib/alexandria/ui/init.rb', line 57

def action_name
  "MoveIn" + name.gsub(/\s/, "")
end

#copy_covers(somewhere) ⇒ Object



381
382
383
384
385
386
387
388
389
390
# File 'lib/alexandria/models/library.rb', line 381

def copy_covers(somewhere)
  FileUtils.rm_rf(somewhere)
  FileUtils.mkdir(somewhere)
  each do |book|
    next unless File.exist?(cover(book))

    FileUtils.cp(cover(book),
                 File.join(somewhere, final_cover(book)))
  end
end

#cover(something) ⇒ Object



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/alexandria/models/library.rb', line 336

def cover(something)
  ident = case something
          when Book
            if something.isbn && !something.isbn.empty?
              something.ident
            else
              "g#{something.ident}" # g is for generated id...
            end
          when String, Integer
            something
          else
            raise NotImplementedError
          end
  File.join(path, ident.to_s + EXT[:cover])
end

#delete(book = nil) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/alexandria/models/library.rb', line 274

def delete(book = nil)
  if book.nil?
    # Delete the whole library.
    raise if @@deleted_libraries.include?(self)

    @@deleted_libraries << self
  else
    if @deleted_books.include?(book)
      doubles = @deleted_books.select { |b| b.equal?(book) }
      unless doubles.empty?
        raise ArgumentError, format(_("Book %<isbn>s was already deleted"),
                                    isbn: book.isbn)
      end
    end
    @deleted_books << book
    i = index(book)
    # We check object IDs there because the user could have added
    # a book with the same identifier as another book he/she
    # previously deleted and that he/she is trying to redo.
    if i && self[i].equal?(book)
      changed
      old_delete(book) # FIX this will old_delete all '==' books
      notify_observers(self, BOOK_REMOVED, book)
    end
  end
end

#deleted?Boolean

Returns:

  • (Boolean)


301
302
303
# File 'lib/alexandria/models/library.rb', line 301

def deleted?
  @@deleted_libraries.include?(self)
end

#final_cover(book) ⇒ Object



396
397
398
399
# File 'lib/alexandria/models/library.rb', line 396

def final_cover(book)
  # TODO: what about PNG?
  book.ident + (Library.jpeg?(cover(book)) ? ".jpg" : ".gif")
end

#n_ratedObject



369
370
371
# File 'lib/alexandria/models/library.rb', line 369

def n_rated
  count { |x| !x.rating.nil? && x.rating > 0 }
end

#n_unratedObject



373
374
375
# File 'lib/alexandria/models/library.rb', line 373

def n_unrated
  length - n_rated
end

#old_cover(book) ⇒ Object



332
333
334
# File 'lib/alexandria/models/library.rb', line 332

def old_cover(book)
  File.join(path, book.ident.to_s + EXT[:cover])
end

#old_deleteObject



273
# File 'lib/alexandria/models/library.rb', line 273

alias old_delete delete

#old_selectObject



323
# File 'lib/alexandria/models/library.rb', line 323

alias old_select select

#pathObject



33
34
35
# File 'lib/alexandria/models/library.rb', line 33

def path
  File.join(@store.library_dir, @name)
end

#really_delete_deleted_booksObject



265
266
267
268
269
270
271
# File 'lib/alexandria/models/library.rb', line 265

def really_delete_deleted_books
  @deleted_books.each do |book|
    [yaml(book), cover(book)].each do |file|
      FileUtils.rm_f(file)
    end
  end
end

#save(book, final = false) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/alexandria/models/library.rb', line 200

def save(book, final = false)
  changed unless final

  # Let's initialize the saved identifier if not already
  # (backward compatibility from 0.4.0).
  book.saved_ident ||= book.ident

  if book.ident != book.saved_ident
    FileUtils.rm(yaml(book.saved_ident))
    if File.exist?(cover(book.saved_ident))
      FileUtils.mv(cover(book.saved_ident), cover(book.ident))
    end

    # Notify before updating the saved identifier, so the views
    # can still use the old one to update their models.
    notify_observers(self, BOOK_UPDATED, book) unless final
    book.saved_ident = book.ident
  end
  # #was File.exist? but that returns true for empty files... CathalMagus
  already_there = (File.size?(yaml(book)) &&
                   !@deleted_books.include?(book))

  temp_book = book.dup
  temp_book.library = nil
  File.open(yaml(temp_book), "w") { |io| io.puts temp_book.to_yaml }

  # Do not notify twice.
  return unless changed?

  notify_observers(self,
                   already_there ? BOOK_UPDATED : BOOK_ADDED,
                   book)
end

#save_cover(book, cover_uri) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/alexandria/models/library.rb', line 239

def save_cover(book, cover_uri)
  Dir.chdir(path) do
    # Fetch the cover picture.
    cover_file = cover(book)
    data = fetch_image(cover_uri)
    if data
      File.write(cover_file, data)

      # Remove the file if its blank.
      File.delete(cover_file) if Alexandria::UI::Icons.blank?(cover_file)
    end
  end
end

#selectObject



324
325
326
327
328
329
330
# File 'lib/alexandria/models/library.rb', line 324

def select
  filtered_library = Library.new(@name)
  each do |book|
    filtered_library << book if yield(book)
  end
  filtered_library
end

#simple_save(book) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/alexandria/models/library.rb', line 182

def simple_save(book)
  # Let's initialize the saved identifier if not already
  # (backward compatibility from 0.4.0)
  # book.saved_ident ||= book.ident
  book.saved_ident = book.ident if book.saved_ident.nil? || book.saved_ident.empty?
  if book.ident != book.saved_ident
    FileUtils.rm(yaml(book.saved_ident))
    if File.exist?(cover(book.saved_ident))
      FileUtils.mv(cover(book.saved_ident), cover(book.ident))
    end
  end
  book.saved_ident = book.ident

  filename = book.saved_ident.to_s + ".yaml"
  File.open(filename, "w") { |io| io.puts book.to_yaml }
  filename
end

#transportObject



234
235
236
237
# File 'lib/alexandria/models/library.rb', line 234

def transport
  config = Alexandria::Preferences.instance.http_proxy_config
  config ? Net::HTTP.Proxy(*config) : Net::HTTP
end

#undelete(book = nil) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/alexandria/models/library.rb', line 305

def undelete(book = nil)
  if book.nil?
    # Undelete the whole library.
    raise unless @@deleted_libraries.include?(self)

    @@deleted_libraries.delete(self)
  else
    raise unless @deleted_books.include?(book)

    @deleted_books.delete(book)
    unless include?(book)
      changed
      self << book
      notify_observers(self, BOOK_ADDED, book)
    end
  end
end

#updating?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/alexandria/models/library.rb', line 37

def updating?
  @updating
end

#yaml(something, basedir = path) ⇒ Object



352
353
354
355
356
357
358
359
360
361
362
# File 'lib/alexandria/models/library.rb', line 352

def yaml(something, basedir = path)
  ident = case something
          when Book
            something.ident
          when String, Integer
            something
          else
            raise NotImplementedError
          end
  File.join(basedir, ident.to_s + EXT[:book])
end