timex_datalink_caldav 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.