timex_datalink_caldav 1.0.0 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e70c9183de68e607682770a4085c86d87ba3cc0d09105c6867b445a702d8808d
4
- data.tar.gz: 6f4248f1fc332ce55c3c7b2850a8b49ccc42d6456301235225032fb2aeb1d80e
3
+ metadata.gz: 8301c0dee4e085d6ebbbebbcd41872211625f6798980f688cf66765550519622
4
+ data.tar.gz: 30171ce1fcdb85fa57b17121982c85ab5805cdbef1f12106d56561397bd82ee6
5
5
  SHA512:
6
- metadata.gz: 925f98b03e5797567d02b7640febc98338c0fc9dacffa0c5771d501c266c9ee4dc59667752b489db851a1f8fe9cc4d4349fab0094ceab3f8f3de4e339731be30
7
- data.tar.gz: 7b78a3c9ae837604bff285c6584f1dc84ae32279ce3f99ee0e32d8037f37dd3832451f07aeacca2a5e102d4a2a4016c1b5c6bc570e2aa9d31f04167975a79288
6
+ metadata.gz: 31a43cf2e995bcd29d51ebae9d3a30002239ac0bea6862f97b0f967daf25734409432e75fbced7727ecc2126e6ec509bdb0c09fd6dc7fcbffed87e9c124d73a5
7
+ data.tar.gz: 5b8b1f14b62a03c6354c2b555651ae6336b6248ddc633b4749b966929c2e8b6e39c8d6f8845cb57329a46a5dc18c35522bd8a193c115efa781922ead52853705
data/Gemfile CHANGED
@@ -23,4 +23,10 @@ gem "tzinfo", "~> 2.0"
23
23
 
24
24
  gem "yaml", "~> 0.2.1"
25
25
 
26
- gem "open-uri", "~> 0.3.0"
26
+ gem "text", "~> 1.3"
27
+
28
+ gem "humanize", "~> 2.5"
29
+
30
+ gem "down", "~> 5.4"
31
+
32
+ gem "ruby-progressbar", "~> 1.13"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- timex_datalink_caldav (0.3.0)
4
+ timex_datalink_caldav (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -21,10 +21,11 @@ GEM
21
21
  nokogiri
22
22
  concurrent-ruby (1.2.2)
23
23
  crc (0.4.2)
24
- date (3.3.3)
25
24
  diff-lcs (1.5.0)
26
25
  domain_name (0.5.20190701)
27
26
  unf (>= 0.0.5, < 1.0.0)
27
+ down (5.4.1)
28
+ addressable (~> 2.8)
28
29
  ffi (1.15.5)
29
30
  ffi-compiler (1.0.1)
30
31
  ffi (>= 1.0.0)
@@ -37,6 +38,7 @@ GEM
37
38
  http-cookie (1.0.5)
38
39
  domain_name (~> 0.5)
39
40
  http-form_data (2.3.0)
41
+ humanize (2.5.1)
40
42
  i18n (1.13.0)
41
43
  concurrent-ruby (~> 1.0)
42
44
  icalendar (2.8.0)
@@ -52,10 +54,6 @@ GEM
52
54
  minitest (5.18.0)
53
55
  nokogiri (1.14.4-arm64-darwin)
54
56
  racc (~> 1.4)
55
- open-uri (0.3.0)
56
- stringio
57
- time
58
- uri
59
57
  public_suffix (5.0.1)
60
58
  racc (1.6.2)
61
59
  rake (13.0.6)
@@ -72,11 +70,10 @@ GEM
72
70
  diff-lcs (>= 1.2.0, < 2.0)
73
71
  rspec-support (~> 3.12.0)
74
72
  rspec-support (3.12.0)
73
+ ruby-progressbar (1.13.0)
75
74
  rubyserial (0.6.0)
76
75
  ffi (~> 1.9, >= 1.9.3)
77
- stringio (3.0.6)
78
- time (0.2.2)
79
- date
76
+ text (1.3.1)
80
77
  timex_datalink_client (0.11.0)
81
78
  activemodel (~> 7.0.4)
82
79
  crc (~> 0.4.2)
@@ -87,7 +84,6 @@ GEM
87
84
  unf (0.1.4)
88
85
  unf_ext
89
86
  unf_ext (0.0.8.2)
90
- uri (0.12.1)
91
87
  yaml (0.2.1)
92
88
 
93
89
  PLATFORMS
@@ -96,11 +92,14 @@ PLATFORMS
96
92
  DEPENDENCIES
97
93
  activesupport (~> 7.0)
98
94
  calendav (~> 0.4.0)
95
+ down (~> 5.4)
96
+ humanize (~> 2.5)
99
97
  icalendar (~> 2.8)
100
98
  icalendar-recurrence (~> 1.1)
101
- open-uri (~> 0.3.0)
102
99
  rake (~> 13.0)
103
100
  rspec (~> 3.12)
101
+ ruby-progressbar (~> 1.13)
102
+ text (~> 1.3)
104
103
  timex_datalink_caldav!
105
104
  timex_datalink_client (~> 0.11.0)
106
105
  tzinfo (~> 2.0)
data/bin/get_ebrain_db ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'down'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+ require 'ruby-progressbar'
7
+
8
+ # Define URL and file names
9
+ url = "https://archive.org/download/ebrain-1.1.6/ebrain-1.1.6.iso"
10
+ iso_file = File.join(Dir.tmpdir, "ebrain-1.1.6.iso")
11
+ msi_file = File.join(Dir.tmpdir, "eBrain.MSI")
12
+ cab_file = File.join(Dir.tmpdir, "Cabs.w4.cab")
13
+ mdb_file1 = File.join(Dir.tmpdir, "pcvocab.mdb1")
14
+ mdb_file = "pcvocab.mdb" # This is in the current directory
15
+
16
+ # Check if 7zip is installed
17
+ unless system('which 7z > /dev/null 2>&1')
18
+ puts "7zip is not installed. Please download and install 7zip from https://www.7-zip.org/download.html"
19
+ exit 1
20
+ end
21
+
22
+ # Initialize the progress bar
23
+ progressbar = nil
24
+
25
+ # Download the ISO file
26
+ puts "Downloading ISO file..."
27
+ Down.download(url,
28
+ destination: iso_file,
29
+ content_length_proc: ->(content_length) {
30
+ progressbar = ProgressBar.create(
31
+ title: 'Download Progress',
32
+ total: content_length,
33
+ format: '%a %bᗧ%i %p%% %t, Estimated time: %e'
34
+ )
35
+ },
36
+ progress_proc: ->(progress) {
37
+ progressbar.progress = progress
38
+ }
39
+ )
40
+
41
+ # Extract the MSI file from the ISO
42
+ puts "Extracting MSI file..."
43
+ system("7z e #{iso_file} -o#{File.dirname(msi_file)} #{File.basename(msi_file)}")
44
+
45
+ # Extract the CAB file from the MSI
46
+ puts "Extracting CAB file..."
47
+ system("7z e #{msi_file} -o#{File.dirname(cab_file)} #{File.basename(cab_file)}")
48
+
49
+ # Extract the MDB file from the CAB
50
+ puts "Extracting MDB file..."
51
+ system("7z e #{cab_file} -o#{File.dirname(mdb_file1)} #{File.basename(mdb_file1)}")
52
+
53
+ # Rename and move the MDB file to the current directory
54
+ puts "Renaming and moving MDB file..."
55
+ FileUtils.mv mdb_file1, mdb_file
56
+
57
+ puts "Done."
@@ -4,6 +4,8 @@ require 'icalendar/recurrence'
4
4
  require 'timex_datalink_client'
5
5
  require 'active_support/time'
6
6
  require 'tzinfo'
7
+ require 'humanize'
8
+ require_relative 'similar_word'
7
9
 
8
10
  module TimexDatalinkCaldav
9
11
  class Client
@@ -15,13 +17,14 @@ module TimexDatalinkCaldav
15
17
  @days_forward = days_forward
16
18
  @protocol_version = protocol_version.to_i
17
19
  @protocol_class = case @protocol_version
18
- when 1 then TimexDatalinkClient::Protocol1
19
- when 3 then TimexDatalinkClient::Protocol3
20
- when 4 then TimexDatalinkClient::Protocol4
21
- else
22
- raise ArgumentError, "Invalid protocol version: #{@protocol_version}"
23
- end
24
- end
20
+ when 1 then TimexDatalinkClient::Protocol1
21
+ when 3 then TimexDatalinkClient::Protocol3
22
+ when 4 then TimexDatalinkClient::Protocol4
23
+ when 7 then TimexDatalinkClient::Protocol7
24
+ else
25
+ raise ArgumentError, "Invalid protocol version: #{@protocol_version}"
26
+ end
27
+ end
25
28
 
26
29
  def get_localzone
27
30
  TZInfo::Timezone.get(TZInfo::Timezone.all_country_zones.detect {|z| z.period_for_local(Time.now).utc_total_offset == Time.now.utc_offset}.identifier)
@@ -59,46 +62,113 @@ module TimexDatalinkCaldav
59
62
  anniversaries = []
60
63
  appointment_map = {} # Used to avoid duplicate appointments
61
64
  anniversary_map = {} # Used to avoid duplicate anniversaries
62
-
65
+
66
+ phrase_builder = SimlarWord.new(database: "pcvocab.mdb") if @protocol_version == 7
67
+
63
68
  events.each do |event|
64
- next unless event
65
-
66
- if event.dtstart && event.dtend && (event.dtend.to_date - event.dtstart.to_date == 1) && event.dtstart.to_datetime.hour == 0 && event.dtstart.to_datetime.min == 0 && event.dtend.to_datetime.hour == 0 && event.dtend.to_datetime.min == 0 # Checking if it is an all day event
67
- occurrences = event.occurrences_between(Time.now, Time.now + @days_forward*24*60*60)
68
- occurrences.each do |occurrence|
69
- est_time = occurrence.start_time.in_time_zone(get_localzone)
70
- key = "#{est_time}_#{event.summary.to_s}"
71
- puts key
72
- unless anniversary_map[key] # Check if the event is already in the map
73
- puts "Adding anniversary: #{event.summary.to_s} at date #{event.dtstart.to_s}"
74
- anniversary = @protocol_class::Eeprom::Anniversary.new(
75
- time: event.dtstart.to_time,
76
- anniversary: event.summary.to_s
77
- )
78
- anniversaries << anniversary
79
- anniversary_map[key] = true
80
- end
69
+ next unless event && event.dtstart && event.dtend
70
+
71
+ summary_words = parse_summary(event)
72
+
73
+ occurrences = event.occurrences_between(Time.now, Time.now + @days_forward*24*60*60)
74
+
75
+ if @protocol_version == 7
76
+ if all_day_event?(event)
77
+ add_anniversary_event(anniversary_map, anniversaries, phrase_builder, summary_words, occurrences)
78
+ else
79
+ add_appointment_event(appointment_map, appointments, phrase_builder, summary_words, occurrences)
81
80
  end
82
- elsif event.dtstart && event.dtend
83
- occurrences = event.occurrences_between(Time.now, Time.now + @days_forward*24*60*60)
84
- occurrences.each do |occurrence|
85
- est_time = occurrence.start_time.in_time_zone(get_localzone)
86
- key = "#{est_time}_#{event.summary.to_s}"
87
- unless appointment_map[key] # Check if the event is already in the map
88
- puts "Adding appointment: #{event.summary.to_s} at time #{est_time}"
89
- appointment = @protocol_class::Eeprom::Appointment.new(
90
- time: est_time,
91
- message: event.summary.to_s
92
- )
93
- appointments << appointment
94
- appointment_map[key] = true
95
- end
81
+ else
82
+ if all_day_event?(event)
83
+ add_anniversary(event, anniversary_map, anniversaries, summary_words, occurrences)
84
+ else
85
+ add_appointment(event, appointment_map, appointments, summary_words, occurrences)
96
86
  end
97
87
  end
98
88
  end
99
-
89
+
100
90
  [appointments, anniversaries]
101
91
  end
92
+
93
+ def all_day_event?(event)
94
+ (event.dtend.to_date - event.dtstart.to_date == 1) && event.dtstart.to_datetime.hour == 0 && event.dtstart.to_datetime.min == 0 && event.dtend.to_datetime.hour == 0 && event.dtend.to_datetime.min == 0
95
+ end
96
+
97
+ def parse_summary(event)
98
+ event.summary.to_s.split.map do |word|
99
+ if word =~ /\A[a-zA-Z]+\z/
100
+ word
101
+ elsif word =~ /\A\d+\z/
102
+ word.to_i.humanize
103
+ end
104
+ end.compact
105
+ end
106
+
107
+ def add_appointment_event(appointment_map, appointments, phrase_builder, summary_words, occurrences)
108
+ occurrences.each do |occurrence|
109
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
110
+ key = "#{est_time}_#{summary_words}"
111
+ unless appointment_map[key]
112
+ puts "Adding appointment event: #{summary_words.join(' ')} at time #{est_time}"
113
+ event_phrase = phrase_builder.vocab_ids_for(*summary_words)
114
+ appointment = @protocol_class::Eeprom::Calendar::Event.new(
115
+ time: est_time,
116
+ phrase: event_phrase
117
+ )
118
+ appointments << appointment
119
+ appointment_map[key] = true
120
+ end
121
+ end
122
+ end
123
+
124
+ def add_anniversary_event(anniversary_map, anniversaries, phrase_builder, summary_words, occurrences)
125
+ occurrences.each do |occurrence|
126
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
127
+ key = "#{est_time}_#{summary_words.join(' ')}"
128
+ unless anniversary_map[key]
129
+ puts "Adding anniversary event: #{summary_words.join(' ')} at date #{event.dtstart.to_s}"
130
+ event_phrase = phrase_builder.vocab_ids_for(*summary_words)
131
+ anniversary = @protocol_class::Eeprom::Calendar::Event.new(
132
+ time: Time.new(est_time.year, est_time.month, est_time.day, 9, 30, 0),
133
+ phrase: event_phrase
134
+ )
135
+ anniversaries << anniversary
136
+ anniversary_map[key] = true
137
+ end
138
+ end
139
+ end
140
+
141
+ def add_appointment(event, appointment_map, appointments, summary_words, occurrences)
142
+ occurrences.each do |occurrence|
143
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
144
+ key = "#{est_time}_#{summary_words}"
145
+ unless appointment_map[key]
146
+ puts "Adding appointment: #{summary_words} at time #{est_time}"
147
+ appointment = @protocol_class::Eeprom::Appointment.new(
148
+ time: est_time,
149
+ message: summary_words
150
+ )
151
+ appointments << appointment
152
+ appointment_map[key] = true
153
+ end
154
+ end
155
+ end
156
+
157
+ def add_anniversary(event, anniversary_map, anniversaries, summary_words, occurrences)
158
+ occurrences.each do |occurrence|
159
+ est_time = occurrence.start_time.in_time_zone(get_localzone)
160
+ key = "#{est_time}_#{summary_words}"
161
+ unless anniversary_map[key]
162
+ puts "Adding anniversary: #{summary_words} at date #{event.dtstart.to_s}"
163
+ anniversary = @protocol_class::Eeprom::Anniversary.new(
164
+ time: event.dtstart.to_time,
165
+ anniversary: summary_words
166
+ )
167
+ anniversaries << anniversary
168
+ anniversary_map[key] = true
169
+ end
170
+ end
171
+ end
102
172
 
103
173
  def write_to_watch(appointments, anniversaries)
104
174
  appointments.sort_by! { |appointment| appointment.time }
@@ -125,7 +195,7 @@ module TimexDatalinkCaldav
125
195
  zone: 2,
126
196
  name: time2.zone
127
197
  )
128
- else
198
+ elsif @protocol_version == 3 || @protocol_version == 4
129
199
  time_model = @protocol_class::Time.new(
130
200
  zone: 1,
131
201
  name: time1.zone,
@@ -143,22 +213,41 @@ module TimexDatalinkCaldav
143
213
  time_name_model = nil # Not needed for protocol version 3 and 4
144
214
  utc_time_name_model = nil # Not needed for protocol version 3 and 4
145
215
 
216
+ else
217
+ time_model = nil
218
+ utc_time_model = nil
219
+ time_name_model = nil
220
+ utc_time_name_model = nil
221
+ end
222
+
223
+ if @protocol_version == 7
224
+ calendar = @protocol_class::Eeprom::Calendar.new(
225
+ time: time1,
226
+ events: appointments
227
+ )
228
+ models = [
229
+ @protocol_class::Sync.new,
230
+ @protocol_class::Start.new,
231
+ @protocol_class::Eeprom.new(
232
+ calendar: calendar
233
+ )
234
+ ]
235
+ else
236
+ models = [
237
+ @protocol_class::Sync.new,
238
+ @protocol_class::Start.new,
239
+ time_model,
240
+ time_name_model,
241
+ utc_time_model,
242
+ utc_time_name_model,
243
+ @protocol_class::Eeprom.new(
244
+ appointments: appointments,
245
+ anniversaries: anniversaries,
246
+ appointment_notification_minutes: 5
247
+ ),
248
+ @protocol_class::End.new
249
+ ].compact # Remove any nil entries
146
250
  end
147
-
148
- models = [
149
- @protocol_class::Sync.new,
150
- @protocol_class::Start.new,
151
- time_model,
152
- time_name_model,
153
- utc_time_model,
154
- utc_time_name_model,
155
- @protocol_class::Eeprom.new(
156
- appointments: appointments,
157
- anniversaries: anniversaries,
158
- appointment_notification_minutes: 5
159
- ),
160
- @protocol_class::End.new
161
- ].compact # Remove any nil entries
162
251
 
163
252
  timex_datalink_client = TimexDatalinkClient.new(
164
253
  serial_device: @serial_device,
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mdb"
4
+ require "text" # import the text gem
5
+
6
+
7
+ class SimlarWord
8
+ class WordNotFound < StandardError; end
9
+
10
+ attr_accessor :database
11
+
12
+ def initialize(database:)
13
+ @database = database
14
+ end
15
+
16
+ def vocab_ids_for(*words)
17
+ words.flat_map do |word|
18
+ vocab = vocab_for_word(word)
19
+
20
+ # If the word is not found, look for a similar sounding word
21
+ if vocab.nil?
22
+ word = find_similar_word(word)
23
+ vocab = vocab_for_word(word)
24
+ end
25
+
26
+ raise(WordNotFound, "#{word} is not a valid word!") unless vocab
27
+
28
+ vocab_links = vocab_links_for_vocab(vocab)
29
+
30
+ vocab_links.map do |vocab_link|
31
+ linked_vocab = vocab_for_vocab_link(vocab_link)
32
+
33
+ linked_vocab[:"PC Index"].to_i
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def find_similar_word(word)
41
+ word_metaphone = Text::Metaphone.double_metaphone(word)
42
+
43
+ # Filter out words that start with '<' before finding the minimum distance
44
+ filtered_vocab_table = vocab_table.reject { |vocab_word| vocab_word[:Label].start_with?('<') }
45
+
46
+ similar_word = filtered_vocab_table.min_by do |vocab_word|
47
+ distance = Text::Levenshtein.distance(
48
+ Text::Metaphone.double_metaphone(vocab_word[:Label]).first,
49
+ word_metaphone.first
50
+ )
51
+ distance
52
+ end
53
+
54
+ if similar_word
55
+ puts "Similar word for '#{word}' is '#{similar_word[:Label]}'"
56
+ similar_word[:Label]
57
+ else
58
+ puts "No similar word found for '#{word}', using original word"
59
+ word
60
+ end
61
+ end
62
+
63
+ def mdb
64
+ @mdb ||= Mdb.open(database)
65
+ end
66
+
67
+ def vocab_table
68
+ @vocab_table ||= mdb["Vocab"]
69
+ end
70
+
71
+ def vocab_links_table
72
+ @vocab_links_table ||= mdb["Vocab Links"]
73
+ end
74
+
75
+ def vocab_for_word(word)
76
+ vocab_table.detect { |vocab| vocab[:Label].casecmp?(word) }
77
+ end
78
+
79
+ def vocab_links_for_vocab(vocab)
80
+ links = vocab_links_table.select { |vocab_link| vocab_link[:"PC Index"] == vocab[:"PC Index"] }
81
+
82
+ links.sort_by { |link| link[:Sequence].to_i }
83
+ end
84
+
85
+ def vocab_for_vocab_link(vocab_link)
86
+ vocab_table.detect { |vocab| vocab[:"PC Index"] == vocab_link[:"eBrain Index"] }
87
+ end
88
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TimexDatalinkCaldav
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  end
29
29
  spec.require_paths = ["lib"]
30
30
  spec.executables << 'timex_datalink_caldav'
31
-
31
+ spec.executables << 'get_ebrain_db'
32
32
  # Uncomment to register a new dependency of your gem
33
33
  # spec.add_dependency "example-gem", "~> 1.0"
34
34
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timex_datalink_caldav
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willy Hardy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-19 00:00:00.000000000 Z
11
+ date: 2023-05-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'Adds a CLI and a feature to pull your next day of calendar events into
14
14
  the Timex Datalink watch. Note: Hardcoded protocol1 and EST timezone. At the moment.'
@@ -16,6 +16,7 @@ email:
16
16
  - zpga8gbp@mailer.me
17
17
  executables:
18
18
  - timex_datalink_caldav
19
+ - get_ebrain_db
19
20
  extensions: []
20
21
  extra_rdoc_files: []
21
22
  files:
@@ -25,11 +26,13 @@ files:
25
26
  - LICENSE
26
27
  - README.md
27
28
  - Rakefile
29
+ - bin/get_ebrain_db
28
30
  - bin/timex_datalink_caldav
29
31
  - examples/README.md
30
32
  - examples/caldav.yaml.sample
31
33
  - lib/timex_datalink_caldav.rb
32
34
  - lib/timex_datalink_caldav/client.rb
35
+ - lib/timex_datalink_caldav/similar_word.rb
33
36
  - lib/timex_datalink_caldav/version.rb
34
37
  - sig/timex_datalink_caldav.rbs
35
38
  - timex_datalink_caldav.gemspec
@@ -56,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
59
  - !ruby/object:Gem::Version
57
60
  version: '0'
58
61
  requirements: []
59
- rubygems_version: 3.4.10
62
+ rubygems_version: 3.4.13
60
63
  signing_key:
61
64
  specification_version: 4
62
65
  summary: Allows the Timex Datalink watch to sync with a CalDAV server.