anki_record 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -2
- data/CHANGELOG.md +37 -9
- data/Gemfile +3 -1
- data/Gemfile.lock +10 -2
- data/README.md +120 -35
- data/anki_record.gemspec +1 -5
- data/lib/anki_record/anki_package/anki_package.rb +237 -0
- data/lib/anki_record/card/card.rb +108 -0
- data/lib/anki_record/card/card_attributes.rb +39 -0
- data/lib/anki_record/card_template/card_template.rb +64 -0
- data/lib/anki_record/card_template/card_template_attributes.rb +69 -0
- data/lib/anki_record/collection/collection.rb +182 -0
- data/lib/anki_record/collection/collection_attributes.rb +35 -0
- data/lib/anki_record/database_setup_constants.rb +88 -0
- data/lib/anki_record/deck/deck.rb +99 -0
- data/lib/anki_record/deck/deck_attributes.rb +30 -0
- data/lib/anki_record/deck/deck_defaults.rb +19 -0
- data/lib/anki_record/{deck_options_group.rb → deck_options_group/deck_options_group.rb} +12 -31
- data/lib/anki_record/deck_options_group/deck_options_group_attributes.rb +23 -0
- data/lib/anki_record/helpers/checksum_helper.rb +10 -11
- data/lib/anki_record/helpers/data_query_helper.rb +15 -0
- data/lib/anki_record/helpers/shared_constants_helper.rb +6 -6
- data/lib/anki_record/helpers/time_helper.rb +18 -13
- data/lib/anki_record/note/note.rb +178 -0
- data/lib/anki_record/note/note_attributes.rb +56 -0
- data/lib/anki_record/note_field/note_field.rb +62 -0
- data/lib/anki_record/note_field/note_field_attributes.rb +39 -0
- data/lib/anki_record/note_field/note_field_defaults.rb +19 -0
- data/lib/anki_record/note_type/note_type.rb +161 -0
- data/lib/anki_record/note_type/note_type_attributes.rb +80 -0
- data/lib/anki_record/note_type/note_type_defaults.rb +38 -0
- data/lib/anki_record/version.rb +1 -1
- data/lib/anki_record.rb +1 -16
- metadata +26 -16
- data/lib/anki_record/anki_package.rb +0 -194
- data/lib/anki_record/card.rb +0 -75
- data/lib/anki_record/card_template.rb +0 -105
- data/lib/anki_record/collection.rb +0 -105
- data/lib/anki_record/db/anki_schema_definition.rb +0 -77
- data/lib/anki_record/db/clean_collection21_record.rb +0 -10
- data/lib/anki_record/db/clean_collection2_record.rb +0 -10
- data/lib/anki_record/deck.rb +0 -101
- data/lib/anki_record/note.rb +0 -135
- data/lib/anki_record/note_field.rb +0 -84
- data/lib/anki_record/note_type.rb +0 -233
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../helpers/shared_constants_helper"
|
4
|
+
require_relative "../helpers/time_helper"
|
5
|
+
require_relative "card_attributes"
|
6
|
+
|
7
|
+
module AnkiRecord
|
8
|
+
##
|
9
|
+
# Card represents an Anki card.
|
10
|
+
class Card
|
11
|
+
include CardAttributes
|
12
|
+
include Helpers::TimeHelper
|
13
|
+
include Helpers::SharedConstantsHelper
|
14
|
+
|
15
|
+
def initialize(note:, card_template: nil, card_data: nil) # :nodoc:
|
16
|
+
@note = note
|
17
|
+
if card_template
|
18
|
+
setup_instance_variables_for_new_card(card_template: card_template)
|
19
|
+
elsif card_data
|
20
|
+
setup_instance_variables_from_existing(card_data: card_data)
|
21
|
+
else
|
22
|
+
raise ArgumentError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def setup_instance_variables_for_new_card(card_template:)
|
29
|
+
raise ArgumentError unless @note.note_type == card_template.note_type
|
30
|
+
|
31
|
+
setup_collaborator_object_instance_variables_for_new_card(card_template: card_template)
|
32
|
+
setup_simple_instance_variables_for_new_card
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup_collaborator_object_instance_variables_for_new_card(card_template:)
|
36
|
+
@card_template = card_template
|
37
|
+
@deck = @note.deck
|
38
|
+
@collection = @deck.collection
|
39
|
+
end
|
40
|
+
|
41
|
+
def setup_simple_instance_variables_for_new_card
|
42
|
+
@id = milliseconds_since_epoch
|
43
|
+
@last_modified_timestamp = seconds_since_epoch
|
44
|
+
@usn = NEW_OBJECT_USN
|
45
|
+
%w[type queue due ivl factor reps lapses left odue odid flags].each do |instance_variable_name|
|
46
|
+
instance_variable_set "@#{instance_variable_name}", 0
|
47
|
+
end
|
48
|
+
@data = "{}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def setup_instance_variables_from_existing(card_data:)
|
52
|
+
setup_collaborator_object_instance_variables_from_existing(card_data: card_data)
|
53
|
+
setup_simple_instance_variables_from_existing(card_data: card_data)
|
54
|
+
end
|
55
|
+
|
56
|
+
def setup_collaborator_object_instance_variables_from_existing(card_data:)
|
57
|
+
@collection = note.note_type.collection
|
58
|
+
@deck = collection.find_deck_by id: card_data["did"]
|
59
|
+
@card_template = note.note_type.card_templates.find do |card_template|
|
60
|
+
card_template.ordinal_number == card_data["ord"]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def setup_simple_instance_variables_from_existing(card_data:)
|
65
|
+
@last_modified_timestamp = card_data["mod"]
|
66
|
+
%w[id usn type queue due ivl factor reps lapses left odue odid flags data].each do |instance_variable_name|
|
67
|
+
instance_variable_set "@#{instance_variable_name}", card_data[instance_variable_name]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
public
|
72
|
+
|
73
|
+
def save(note_exists_already: false) # :nodoc:
|
74
|
+
note_exists_already ? update_card_in_collection_anki21 : insert_new_card_in_collection_anki21
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def update_card_in_collection_anki21
|
80
|
+
statement = @collection.anki_package.prepare <<~SQL
|
81
|
+
update cards set nid = ?, did = ?, ord = ?, mod = ?, usn = ?, type = ?,
|
82
|
+
queue = ?, due = ?, ivl = ?, factor = ?, reps = ?, lapses = ?,
|
83
|
+
left = ?, odue = ?, odid = ?, flags = ?, data = ? where id = ?
|
84
|
+
SQL
|
85
|
+
statement.execute [@note.id, @deck.id, ordinal_number,
|
86
|
+
@last_modified_timestamp, @usn, @type, @queue,
|
87
|
+
@due, @ivl, @factor, @reps,
|
88
|
+
@lapses, @left, @odue, @odid, @flags, @data, @id]
|
89
|
+
end
|
90
|
+
|
91
|
+
def insert_new_card_in_collection_anki21
|
92
|
+
statement = @collection.anki_package.prepare <<~SQL
|
93
|
+
insert into cards (id, nid, did, ord,
|
94
|
+
mod, usn, type, queue,
|
95
|
+
due, ivl, factor, reps,
|
96
|
+
lapses, left, odue, odid, flags, data)
|
97
|
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
98
|
+
SQL
|
99
|
+
statement.execute [@id, @note.id, @deck.id, ordinal_number,
|
100
|
+
@last_modified_timestamp, @usn, @type, @queue,
|
101
|
+
@due, @ivl, @factor, @reps, @lapses, @left, @odue, @odid, @flags, @data]
|
102
|
+
end
|
103
|
+
|
104
|
+
def ordinal_number
|
105
|
+
@card_template&.ordinal_number
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
##
|
5
|
+
# Module with the Card class's attribute readers, writers, and accessors.
|
6
|
+
module CardAttributes
|
7
|
+
##
|
8
|
+
# The card's note object.
|
9
|
+
attr_reader :note
|
10
|
+
|
11
|
+
##
|
12
|
+
# The card's deck object.
|
13
|
+
attr_reader :deck
|
14
|
+
|
15
|
+
##
|
16
|
+
# The card's collection object.
|
17
|
+
attr_reader :collection
|
18
|
+
|
19
|
+
##
|
20
|
+
# The card's card template object.
|
21
|
+
attr_reader :card_template
|
22
|
+
|
23
|
+
##
|
24
|
+
# The card's id.
|
25
|
+
#
|
26
|
+
# This is also the number of milliseconds since the 1970 epoch at which the card was created.
|
27
|
+
attr_reader :id
|
28
|
+
|
29
|
+
##
|
30
|
+
# The number of seconds since the 1970 epoch at which the card was last modified.
|
31
|
+
attr_reader :last_modified_timestamp
|
32
|
+
|
33
|
+
##
|
34
|
+
# The card's update sequence number.
|
35
|
+
attr_reader :usn
|
36
|
+
|
37
|
+
attr_reader :type, :queue, :due, :ivl, :factor, :reps, :lapses, :left, :odue, :odid, :flags, :data
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "card_template_attributes"
|
4
|
+
|
5
|
+
module AnkiRecord
|
6
|
+
##
|
7
|
+
# CardTemplate represents a card template of an Anki note type.
|
8
|
+
class CardTemplate
|
9
|
+
include CardTemplateAttributes
|
10
|
+
|
11
|
+
# Instantiates a new card template with name +name+ for the note type +note_type+.
|
12
|
+
def initialize(note_type:, name: nil, args: nil)
|
13
|
+
raise ArgumentError unless (name && args.nil?) || (args && args["name"])
|
14
|
+
|
15
|
+
@note_type = note_type
|
16
|
+
if args
|
17
|
+
setup_card_template_instance_variables_from_existing(args: args)
|
18
|
+
else
|
19
|
+
setup_card_template_instance_variables(name: name)
|
20
|
+
end
|
21
|
+
|
22
|
+
@note_type.add_card_template self
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h # :nodoc:
|
26
|
+
{
|
27
|
+
name: @name,
|
28
|
+
ord: @ordinal_number,
|
29
|
+
qfmt: @question_format, afmt: @answer_format,
|
30
|
+
bqfmt: @bqfmt,
|
31
|
+
bafmt: @bafmt,
|
32
|
+
did: @deck_id,
|
33
|
+
bfont: @browser_font_style,
|
34
|
+
bsize: @browser_font_size
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def setup_card_template_instance_variables_from_existing(args:)
|
41
|
+
@name = args["name"]
|
42
|
+
@ordinal_number = args["ord"]
|
43
|
+
@question_format = args["qfmt"]
|
44
|
+
@answer_format = args["afmt"]
|
45
|
+
@bqfmt = args["bqfmt"]
|
46
|
+
@bafmt = args["bafmt"]
|
47
|
+
@deck_id = args["did"]
|
48
|
+
@browser_font_style = args["bfont"]
|
49
|
+
@browser_font_size = args["bsize"]
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_card_template_instance_variables(name:)
|
53
|
+
@name = name
|
54
|
+
@ordinal_number = @note_type.card_templates.length
|
55
|
+
@question_format = ""
|
56
|
+
@answer_format = ""
|
57
|
+
@bqfmt = ""
|
58
|
+
@bafmt = ""
|
59
|
+
@deck_id = nil
|
60
|
+
@browser_font_style = ""
|
61
|
+
@browser_font_size = 0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
##
|
5
|
+
# Module with the CardTemplate class's attribute readers, writers, and accessors.
|
6
|
+
module CardTemplateAttributes
|
7
|
+
##
|
8
|
+
# The card template's name.
|
9
|
+
attr_accessor :name
|
10
|
+
|
11
|
+
##
|
12
|
+
# The card template's font style in the browser.
|
13
|
+
attr_accessor :browser_font_style
|
14
|
+
|
15
|
+
##
|
16
|
+
# The card template's font size used in the browser.
|
17
|
+
attr_accessor :browser_font_size
|
18
|
+
|
19
|
+
##
|
20
|
+
# The card template's question format.
|
21
|
+
attr_reader :question_format
|
22
|
+
|
23
|
+
##
|
24
|
+
# Sets the question format of the card template.
|
25
|
+
#
|
26
|
+
# Raises an ArgumentError if the specified format attempts to use invalid fields.
|
27
|
+
def question_format=(format)
|
28
|
+
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
29
|
+
capture.chomp("}}").reverse.chomp("{{").reverse
|
30
|
+
end
|
31
|
+
if fields_in_specified_format.any? do |field_name|
|
32
|
+
!note_type.allowed_card_template_question_format_field_names.include?(field_name)
|
33
|
+
end
|
34
|
+
raise ArgumentError, "You tried to use a field that the note type does not have."
|
35
|
+
end
|
36
|
+
|
37
|
+
@question_format = format
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# The card template's answer format.
|
42
|
+
attr_reader :answer_format
|
43
|
+
|
44
|
+
##
|
45
|
+
# Sets the answer format of the card template.
|
46
|
+
#
|
47
|
+
# Raises an ArgumentError if the specified format attempts to use invalid fields.
|
48
|
+
def answer_format=(format)
|
49
|
+
fields_in_specified_format = format.scan(/{{.+?}}/).map do |capture|
|
50
|
+
capture.chomp("}}").reverse.chomp("{{").reverse
|
51
|
+
end
|
52
|
+
if fields_in_specified_format.any? do |field_name|
|
53
|
+
!note_type.allowed_card_template_answer_format_field_names.include?(field_name)
|
54
|
+
end
|
55
|
+
raise ArgumentError, "You tried to use a field that the note type does not have."
|
56
|
+
end
|
57
|
+
|
58
|
+
@answer_format = format
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# The card template's note type object.
|
63
|
+
attr_reader :note_type
|
64
|
+
|
65
|
+
##
|
66
|
+
# 0 for the first card template of the note type, 1 for the second, etc.
|
67
|
+
attr_reader :ordinal_number
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
require_relative "../deck/deck"
|
6
|
+
require_relative "../deck_options_group/deck_options_group"
|
7
|
+
require_relative "../helpers/data_query_helper"
|
8
|
+
require_relative "../helpers/time_helper"
|
9
|
+
require_relative "../note_type/note_type"
|
10
|
+
require_relative "collection_attributes"
|
11
|
+
|
12
|
+
module AnkiRecord
|
13
|
+
##
|
14
|
+
# Collection represents the single record in the Anki collection.anki21 database's `col` table.
|
15
|
+
# The note types, decks, and deck options groups data are contained within this record.
|
16
|
+
class Collection
|
17
|
+
include Helpers::DataQueryHelper
|
18
|
+
include Helpers::TimeHelper
|
19
|
+
include CollectionAttributes
|
20
|
+
|
21
|
+
def initialize(anki_package:) # :nodoc:
|
22
|
+
setup_collection_instance_variables(anki_package: anki_package)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_note_type(note_type) # :nodoc:
|
26
|
+
raise ArgumentError unless note_type.instance_of?(AnkiRecord::NoteType)
|
27
|
+
|
28
|
+
existing_note_type = nil
|
29
|
+
@note_types.each do |nt|
|
30
|
+
existing_note_type = nt if nt.id == note_type.id
|
31
|
+
end
|
32
|
+
@note_types.delete(existing_note_type) if existing_note_type
|
33
|
+
|
34
|
+
@note_types << note_type
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_deck(deck) # :nodoc:
|
38
|
+
raise ArgumentError unless deck.instance_of?(AnkiRecord::Deck)
|
39
|
+
|
40
|
+
@decks << deck
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_deck_options_group(deck_options_group) # :nodoc:
|
44
|
+
raise ArgumentError unless deck_options_group.instance_of?(AnkiRecord::DeckOptionsGroup)
|
45
|
+
|
46
|
+
@deck_options_groups << deck_options_group
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Returns the collection's note type object found by either +name+ or +id+, or nil if it is not found.
|
51
|
+
def find_note_type_by(name: nil, id: nil)
|
52
|
+
if (id && name) || (id.nil? && name.nil?)
|
53
|
+
raise ArgumentError,
|
54
|
+
"You must pass either an id or name keyword argument."
|
55
|
+
end
|
56
|
+
|
57
|
+
name ? find_note_type_by_name(name: name) : find_note_type_by_id(id: id)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def find_note_type_by_name(name:)
|
63
|
+
note_types.find { |note_type| note_type.name == name }
|
64
|
+
end
|
65
|
+
|
66
|
+
def find_note_type_by_id(id:)
|
67
|
+
note_types.find { |note_type| note_type.id == id }
|
68
|
+
end
|
69
|
+
|
70
|
+
public
|
71
|
+
|
72
|
+
##
|
73
|
+
# Returns the collection's deck object found by either +name+ or +id+, or nil if it is not found.
|
74
|
+
def find_deck_by(name: nil, id: nil)
|
75
|
+
if (id && name) || (id.nil? && name.nil?)
|
76
|
+
raise ArgumentError,
|
77
|
+
"You must pass either an id or name keyword argument."
|
78
|
+
end
|
79
|
+
|
80
|
+
name ? find_deck_by_name(name: name) : find_deck_by_id(id: id)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def find_deck_by_name(name:)
|
86
|
+
decks.find { |deck| deck.name == name }
|
87
|
+
end
|
88
|
+
|
89
|
+
def find_deck_by_id(id:)
|
90
|
+
decks.find { |deck| deck.id == id }
|
91
|
+
end
|
92
|
+
|
93
|
+
public
|
94
|
+
|
95
|
+
##
|
96
|
+
# Returns the collection's deck options group object found by +id+, or nil if it is not found.
|
97
|
+
def find_deck_options_group_by(id:)
|
98
|
+
deck_options_groups.find { |deck_options_group| deck_options_group.id == id }
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Returns the collection's note object found by +id+, or nil if it is not found.
|
103
|
+
def find_note_by(id:)
|
104
|
+
note_cards_data = note_cards_data_for_note_id sql_able: anki_package, id: id
|
105
|
+
return nil unless note_cards_data
|
106
|
+
|
107
|
+
AnkiRecord::Note.new collection: self, data: note_cards_data
|
108
|
+
end
|
109
|
+
|
110
|
+
# :nodoc:
|
111
|
+
def decks_json
|
112
|
+
JSON.parse(anki_package.prepare("select decks from col;").execute.first["decks"])
|
113
|
+
end
|
114
|
+
|
115
|
+
# :nodoc:
|
116
|
+
def models_json
|
117
|
+
JSON.parse(anki_package.prepare("select models from col;").execute.first["models"])
|
118
|
+
end
|
119
|
+
|
120
|
+
def copy_over_existing(col_record:) # :nodoc:
|
121
|
+
@col_record = col_record
|
122
|
+
setup_simple_collaborator_objects
|
123
|
+
setup_custom_collaborator_objects
|
124
|
+
remove_instance_variable(:@col_record)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def setup_collection_instance_variables(anki_package:)
|
130
|
+
@anki_package = anki_package
|
131
|
+
setup_simple_collaborator_objects
|
132
|
+
setup_custom_collaborator_objects
|
133
|
+
remove_instance_variable(:@col_record)
|
134
|
+
end
|
135
|
+
|
136
|
+
def col_record
|
137
|
+
@col_record ||= @anki_package.prepare("select * from col").execute.first
|
138
|
+
end
|
139
|
+
|
140
|
+
# rubocop:disable Metrics/AbcSize
|
141
|
+
def setup_simple_collaborator_objects
|
142
|
+
@id = col_record["id"]
|
143
|
+
@created_at_timestamp = col_record["crt"]
|
144
|
+
@last_modified_timestamp = col_record["mod"]
|
145
|
+
@scm = col_record["scm"]
|
146
|
+
@ver = col_record["ver"]
|
147
|
+
@dty = col_record["dty"]
|
148
|
+
@usn = col_record["usn"]
|
149
|
+
@ls = col_record["ls"]
|
150
|
+
@configuration = JSON.parse(col_record["conf"])
|
151
|
+
@tags = JSON.parse(col_record["tags"])
|
152
|
+
end
|
153
|
+
# rubocop:enable Metrics/AbcSize
|
154
|
+
|
155
|
+
def setup_custom_collaborator_objects
|
156
|
+
setup_note_type_collaborators
|
157
|
+
setup_deck_options_groups_collaborators
|
158
|
+
setup_deck_collaborators
|
159
|
+
end
|
160
|
+
|
161
|
+
def setup_note_type_collaborators
|
162
|
+
@note_types = []
|
163
|
+
JSON.parse(col_record["models"]).values.map do |model_hash|
|
164
|
+
NoteType.new(collection: self, args: model_hash)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def setup_deck_collaborators
|
169
|
+
@decks = []
|
170
|
+
JSON.parse(col_record["decks"]).values.map do |deck_hash|
|
171
|
+
Deck.new(collection: self, args: deck_hash)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def setup_deck_options_groups_collaborators
|
176
|
+
@deck_options_groups = []
|
177
|
+
JSON.parse(col_record["dconf"]).values.map do |dconf_hash|
|
178
|
+
DeckOptionsGroup.new(collection: self, args: dconf_hash)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
##
|
5
|
+
# Module with the Collection class's attribute readers, writers, and accessors.
|
6
|
+
module CollectionAttributes
|
7
|
+
##
|
8
|
+
# The collection's Anki package object.
|
9
|
+
attr_reader :anki_package
|
10
|
+
|
11
|
+
##
|
12
|
+
# The collection's id, which is also the id of the col record in the collection.anki21 database (usually 1).
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
##
|
16
|
+
# The number of milliseconds since the 1970 epoch when the collection record was created.
|
17
|
+
attr_reader :created_at_timestamp
|
18
|
+
|
19
|
+
##
|
20
|
+
# The number of milliseconds since the 1970 epoch at which the collection record was last modified.
|
21
|
+
attr_reader :last_modified_timestamp
|
22
|
+
|
23
|
+
##
|
24
|
+
# The collection's note type objects as an array.
|
25
|
+
attr_reader :note_types
|
26
|
+
|
27
|
+
##
|
28
|
+
# The collection's deck objects as an array
|
29
|
+
attr_reader :decks
|
30
|
+
|
31
|
+
##
|
32
|
+
# The collection's deck option group objects as an array.
|
33
|
+
attr_reader :deck_options_groups
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnkiRecord
|
4
|
+
# :nodoc:
|
5
|
+
ANKI_SCHEMA_DEFINITION = <<~SQL
|
6
|
+
CREATE TABLE col (
|
7
|
+
id integer PRIMARY KEY,
|
8
|
+
crt integer NOT NULL,
|
9
|
+
mod integer NOT NULL,
|
10
|
+
scm integer NOT NULL,
|
11
|
+
ver integer NOT NULL,
|
12
|
+
dty integer NOT NULL,
|
13
|
+
usn integer NOT NULL,
|
14
|
+
ls integer NOT NULL,
|
15
|
+
conf text NOT NULL,
|
16
|
+
models text NOT NULL,
|
17
|
+
decks text NOT NULL,
|
18
|
+
dconf text NOT NULL,
|
19
|
+
tags text NOT NULL
|
20
|
+
);
|
21
|
+
CREATE TABLE notes (
|
22
|
+
id integer PRIMARY KEY,
|
23
|
+
guid text NOT NULL,
|
24
|
+
mid integer NOT NULL,
|
25
|
+
mod integer NOT NULL,
|
26
|
+
usn integer NOT NULL,
|
27
|
+
tags text NOT NULL,
|
28
|
+
flds text NOT NULL,
|
29
|
+
sfld integer NOT NULL,
|
30
|
+
csum integer NOT NULL,
|
31
|
+
flags integer NOT NULL,
|
32
|
+
data text NOT NULL
|
33
|
+
);
|
34
|
+
CREATE TABLE cards (
|
35
|
+
id integer PRIMARY KEY,
|
36
|
+
nid integer NOT NULL,
|
37
|
+
did integer NOT NULL,
|
38
|
+
ord integer NOT NULL,
|
39
|
+
mod integer NOT NULL,
|
40
|
+
usn integer NOT NULL,
|
41
|
+
type integer NOT NULL,
|
42
|
+
queue integer NOT NULL,
|
43
|
+
due integer NOT NULL,
|
44
|
+
ivl integer NOT NULL,
|
45
|
+
factor integer NOT NULL,
|
46
|
+
reps integer NOT NULL,
|
47
|
+
lapses integer NOT NULL,
|
48
|
+
left integer NOT NULL,
|
49
|
+
odue integer NOT NULL,
|
50
|
+
odid integer NOT NULL,
|
51
|
+
flags integer NOT NULL,
|
52
|
+
data text NOT NULL
|
53
|
+
);
|
54
|
+
CREATE TABLE revlog (
|
55
|
+
id integer PRIMARY KEY,
|
56
|
+
cid integer NOT NULL,
|
57
|
+
usn integer NOT NULL,
|
58
|
+
ease integer NOT NULL,
|
59
|
+
ivl integer NOT NULL,
|
60
|
+
lastIvl integer NOT NULL,
|
61
|
+
factor integer NOT NULL,
|
62
|
+
time integer NOT NULL,
|
63
|
+
type integer NOT NULL
|
64
|
+
);
|
65
|
+
CREATE INDEX ix_notes_usn ON notes (usn);
|
66
|
+
CREATE INDEX ix_cards_usn ON cards (usn);
|
67
|
+
CREATE INDEX ix_revlog_usn ON revlog (usn);
|
68
|
+
CREATE INDEX ix_cards_nid ON cards (nid);
|
69
|
+
CREATE INDEX ix_cards_sched ON cards (did, queue, due);
|
70
|
+
CREATE INDEX ix_revlog_cid ON revlog (cid);
|
71
|
+
CREATE INDEX ix_notes_csum ON notes (csum);
|
72
|
+
CREATE TABLE graves (
|
73
|
+
usn integer NOT NULL,
|
74
|
+
oid integer NOT NULL,
|
75
|
+
type integer NOT NULL
|
76
|
+
);
|
77
|
+
SQL
|
78
|
+
|
79
|
+
# :nodoc:
|
80
|
+
INSERT_COLLECTION_ANKI_2_COL_RECORD = <<~SQL
|
81
|
+
INSERT INTO col VALUES(1,1676883600,1676902390012,1676902390005,11,0,0,0,'{"addToCur":true,"_deck_1_lastNotetype":1676902390008,"nextPos":2,"sortType":"noteFld","newSpread":0,"schedVer":2,"collapseTime":1200,"estTimes":true,"curDeck":1,"creationOffset":300,"dayLearnFirst":false,"timeLim":0,"activeDecks":[1],"sortBackwards":false,"_nt_1676902390008_lastDeck":1,"curModel":1676902390008,"dueCounts":true}','{"1676902390010":{"id":1676902390010,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902390009":{"id":1676902390009,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]},"1676902390008":{"id":1676902390008,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902390011":{"id":1676902390011,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902390012":{"id":1676902390012,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
82
|
+
SQL
|
83
|
+
|
84
|
+
# :nodoc:
|
85
|
+
INSERT_COLLECTION_ANKI_21_COL_RECORD = <<~SQL
|
86
|
+
INSERT INTO col VALUES(1,1676883600,0,1676902364657,11,0,0,0,'{"activeDecks":[1],"curDeck":1,"nextPos":1,"schedVer":2,"sortType":"noteFld","estTimes":true,"collapseTime":1200,"dayLearnFirst":false,"curModel":1676902364661,"sortBackwards":false,"newSpread":0,"creationOffset":300,"timeLim":0,"addToCur":true,"dueCounts":true}','{"1676902364664":{"id":1676902364664,"name":"Basic (type in the answer)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}\\n\\n{{type:Back}}","afmt":"{{Front}}\\n\\n<hr id=answer>\\n\\n{{type:Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0,1]]]},"1676902364663":{"id":1676902364663,"name":"Basic (optional reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{#Add Reverse}}{{Back}}{{/Add Reverse}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Add Reverse","ord":2,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"all",[1,2]]]},"1676902364665":{"id":1676902364665,"name":"Cloze","type":1,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Cloze","ord":0,"qfmt":"{{cloze:Text}}","afmt":"{{cloze:Text}}<br>\\n{{Back Extra}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Text","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back Extra","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n.cloze {\\n font-weight: bold;\\n color: blue;\\n}\\n.nightMode .cloze {\\n color: lightblue;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364661":{"id":1676902364661,"name":"Basic","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]]]},"1676902364662":{"id":1676902364662,"name":"Basic (and reversed card)","type":0,"mod":0,"usn":0,"sortf":0,"did":null,"tmpls":[{"name":"Card 1","ord":0,"qfmt":"{{Front}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Back}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0},{"name":"Card 2","ord":1,"qfmt":"{{Back}}","afmt":"{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{Front}}","bqfmt":"","bafmt":"","did":null,"bfont":"","bsize":0}],"flds":[{"name":"Front","ord":0,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""},{"name":"Back","ord":1,"sticky":false,"rtl":false,"font":"Arial","size":20,"description":""}],"css":".card {\\n font-family: arial;\\n font-size: 20px;\\n text-align: center;\\n color: black;\\n background-color: white;\\n}\\n","latexPre":"\\\\documentclass[12pt]{article}\\n\\\\special{papersize=3in,5in}\\n\\\\usepackage[utf8]{inputenc}\\n\\\\usepackage{amssymb,amsmath}\\n\\\\pagestyle{empty}\\n\\\\setlength{\\\\parindent}{0in}\\n\\\\begin{document}\\n","latexPost":"\\\\end{document}","latexsvg":false,"req":[[0,"any",[0]],[1,"any",[1]]]}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"lrnToday":[0,0],"revToday":[0,0],"newToday":[0,0],"timeToday":[0,0],"collapsed":true,"browserCollapsed":true,"desc":"","dyn":0,"conf":1,"extendNew":0,"extendRev":0}}','{"1":{"id":1,"mod":0,"name":"Default","usn":0,"maxTaken":60,"autoplay":true,"timer":0,"replayq":true,"new":{"bury":false,"delays":[1.0,10.0],"initialFactor":2500,"ints":[1,4,0],"order":1,"perDay":20},"rev":{"bury":false,"ease4":1.3,"ivlFct":1.0,"maxIvl":36500,"perDay":200,"hardFactor":1.2},"lapse":{"delays":[10.0],"leechAction":1,"leechFails":8,"minInt":1,"mult":0.0},"dyn":false,"newMix":0,"newPerDayMinimum":0,"interdayLearningMix":0,"reviewOrder":0,"newSortOrder":0,"newGatherPriority":0,"buryInterdayLearning":false}}','{}');
|
87
|
+
SQL
|
88
|
+
end
|