Module: Bookflow::AppleBooks::Extract

Defined in:
lib/bookflow/apple_books/extract.rb

Class Method Summary collapse

Class Method Details

.blank_text_row?(row) ⇒ Boolean

Returns:

  • (Boolean)


45
46
47
48
49
50
# File 'lib/bookflow/apple_books/extract.rb', line 45

def blank_text_row?(row)
  selected = row["ZANNOTATIONSELECTEDTEXT"].to_s
  representative = row["ZANNOTATIONREPRESENTATIVETEXT"].to_s
  note = row["ZANNOTATIONNOTE"].to_s
  selected.empty? && representative.empty? && note.empty?
end

.build_annotation(row) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/bookflow/apple_books/extract.rb', line 64

def build_annotation(row)
  selected_text = row["ZANNOTATIONSELECTEDTEXT"].to_s
  representative_text = row["ZANNOTATIONREPRESENTATIVETEXT"]
  highlight_text = selected_text.empty? ? representative_text : selected_text

  Annotation.new(
    annotation_id: row["Z_PK"],
    asset_id: row["ZANNOTATIONASSETID"],
    highlight_text: highlight_text,
    note_text: row["ZANNOTATIONNOTE"],
    created_at: parse_time(row["ZANNOTATIONCREATIONDATE"]),
    modified_at: parse_time(row["ZANNOTATIONMODIFICATIONDATE"]),
    absolute_location: row["ZPLABSOLUTEPHYSICALLOCATION"],
    range_start: row["ZPLLOCATIONRANGESTART"],
    range_end: row["ZPLLOCATIONRANGEEND"]
  )
end

.build_books_lookup(rows) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/bookflow/apple_books/extract.rb', line 52

def build_books_lookup(rows)
  rows.each_with_object({}) do |row, memo|
    asset_id = row["ZASSETID"].to_s
    memo[asset_id] = Book.new(
      asset_id: asset_id,
      title: row["ZTITLE"],
      author: row["ZAUTHOR"],
      source_path: row["ZPATH"]
    )
  end
end

.filtered_out?(book, book_filter) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
41
42
43
# File 'lib/bookflow/apple_books/extract.rb', line 38

def filtered_out?(book, book_filter)
  return false if book_filter.nil? || book_filter.strip.empty?

  haystack = "#{book.title} #{book.author}".downcase
  !haystack.include?(book_filter.downcase)
end

.group_exports(annotation_rows:, book_rows:, book_filter:) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/bookflow/apple_books/extract.rb', line 8

def group_exports(annotation_rows:, book_rows:, book_filter:)
  books_by_asset_id = build_books_lookup(book_rows)
  grouped_annotations = Hash.new { |hash, key| hash[key] = [] }

  annotation_rows.each do |row|
    next if blank_text_row?(row)

    asset_id = row["ZANNOTATIONASSETID"].to_s
    book = books_by_asset_id[asset_id] || unknown_book(asset_id)
    next if filtered_out?(book, book_filter)

    grouped_annotations[book] << build_annotation(row)
  end

  grouped_annotations.map do |book, annotations|
    BookExport.new(book: book, annotations: sort_annotations(annotations))
  end.sort_by { |book_export| [book_export.book.title.to_s.downcase, book_export.book.asset_id.to_s] }
end

.parse_time(value) ⇒ Object



82
83
84
85
86
87
88
89
# File 'lib/bookflow/apple_books/extract.rb', line 82

def parse_time(value)
  return value if value.is_a?(Time)
  return nil if value.nil? || value.to_s.strip.empty?

  Time.parse(value.to_s)
rescue ArgumentError
  nil
end

.sort_annotations(annotations) ⇒ Object



27
28
29
30
31
32
33
34
35
36
# File 'lib/bookflow/apple_books/extract.rb', line 27

def sort_annotations(annotations)
  annotations.sort_by do |annotation|
    [
      annotation.absolute_location || Float::INFINITY,
      annotation.range_start || Float::INFINITY,
      annotation.created_at || Time.at(0).utc,
      annotation.annotation_id || 0
    ]
  end
end

.unknown_book(asset_id) ⇒ Object



91
92
93
# File 'lib/bookflow/apple_books/extract.rb', line 91

def unknown_book(asset_id)
  Book.new(asset_id: asset_id, title: "Unknown Book", author: nil, source_path: nil)
end