timex_datalink_caldav 0.2.0 → 1.0.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: 6c7e200c3fbb1011bd0b0b67b98db758f8802b72d8686ceb991b4766cb285860
4
- data.tar.gz: e5b0052df0811f4af6f52c34727bd26bd93254903fdc218e9be00d51ae5ac169
3
+ metadata.gz: e70c9183de68e607682770a4085c86d87ba3cc0d09105c6867b445a702d8808d
4
+ data.tar.gz: 6f4248f1fc332ce55c3c7b2850a8b49ccc42d6456301235225032fb2aeb1d80e
5
5
  SHA512:
6
- metadata.gz: b07bd1855b0b9ffe5a36301c239df18f588bacfd7c1ba6f3e20988057cab7491343dc86072ad43bd5194e0c67e5d25ccb9d4f252214953dd8ed2a793819d9a72
7
- data.tar.gz: 529e2275b9207f501414c41131df0e505ae85e46ae8319a4ab0c04a35275b85be393c84b022e0e10890e3072ff4969a351aa55da453a3183b240bc9db19df94f
6
+ metadata.gz: 925f98b03e5797567d02b7640febc98338c0fc9dacffa0c5771d501c266c9ee4dc59667752b489db851a1f8fe9cc4d4349fab0094ceab3f8f3de4e339731be30
7
+ data.tar.gz: 7b78a3c9ae837604bff285c6584f1dc84ae32279ce3f99ee0e32d8037f37dd3832451f07aeacca2a5e102d4a2a4016c1b5c6bc570e2aa9d31f04167975a79288
data/Gemfile CHANGED
@@ -18,3 +18,9 @@ gem "icalendar", "~> 2.8"
18
18
  gem "icalendar-recurrence", "~> 1.1"
19
19
 
20
20
  gem "activesupport", "~> 7.0"
21
+
22
+ gem "tzinfo", "~> 2.0"
23
+
24
+ gem "yaml", "~> 0.2.1"
25
+
26
+ gem "open-uri", "~> 0.3.0"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- timex_datalink_caldav (0.1.0)
4
+ timex_datalink_caldav (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -21,6 +21,7 @@ GEM
21
21
  nokogiri
22
22
  concurrent-ruby (1.2.2)
23
23
  crc (0.4.2)
24
+ date (3.3.3)
24
25
  diff-lcs (1.5.0)
25
26
  domain_name (0.5.20190701)
26
27
  unf (>= 0.0.5, < 1.0.0)
@@ -51,6 +52,10 @@ GEM
51
52
  minitest (5.18.0)
52
53
  nokogiri (1.14.4-arm64-darwin)
53
54
  racc (~> 1.4)
55
+ open-uri (0.3.0)
56
+ stringio
57
+ time
58
+ uri
54
59
  public_suffix (5.0.1)
55
60
  racc (1.6.2)
56
61
  rake (13.0.6)
@@ -69,6 +74,9 @@ GEM
69
74
  rspec-support (3.12.0)
70
75
  rubyserial (0.6.0)
71
76
  ffi (~> 1.9, >= 1.9.3)
77
+ stringio (3.0.6)
78
+ time (0.2.2)
79
+ date
72
80
  timex_datalink_client (0.11.0)
73
81
  activemodel (~> 7.0.4)
74
82
  crc (~> 0.4.2)
@@ -79,6 +87,8 @@ GEM
79
87
  unf (0.1.4)
80
88
  unf_ext
81
89
  unf_ext (0.0.8.2)
90
+ uri (0.12.1)
91
+ yaml (0.2.1)
82
92
 
83
93
  PLATFORMS
84
94
  arm64-darwin-22
@@ -88,10 +98,13 @@ DEPENDENCIES
88
98
  calendav (~> 0.4.0)
89
99
  icalendar (~> 2.8)
90
100
  icalendar-recurrence (~> 1.1)
101
+ open-uri (~> 0.3.0)
91
102
  rake (~> 13.0)
92
103
  rspec (~> 3.12)
93
104
  timex_datalink_caldav!
94
105
  timex_datalink_client (~> 0.11.0)
106
+ tzinfo (~> 2.0)
107
+ yaml (~> 0.2.1)
95
108
 
96
109
  BUNDLED WITH
97
110
  2.4.10
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Willy Hardy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # TimexDatalinkCaldav
2
2
 
3
- TimexDatalinkCaldav is a simple Ruby gem designed to sync events from a CalDAV server to a Timex Datalink watch. It can also be used as a standalone command-line interface (CLI) tool.
3
+ TimexDatalinkCaldav is a simple Ruby gem designed to sync events from a CalDAV server or an ical formatted ics file to a Timex Datalink watch. It can also be used as a standalone command-line interface (CLI) tool.
4
+
5
+ ## Pre-requisites
6
+
7
+ If you need to install Ruby, follow the Ruby installation instructions [here](https://www.ruby-lang.org/en/documentation/installation/).
4
8
 
5
9
  ## Installation
6
10
 
@@ -13,9 +17,7 @@ gem install timex_datalink_caldav
13
17
  Or add this line to your application's Gemfile:
14
18
 
15
19
  ```ruby
16
- source "https://rubygems.pkg.github.com/wjhrdy" do
17
- gem "timex_datalink_caldav"
18
- end
20
+ gem "timex_datalink_caldav"
19
21
  ```
20
22
 
21
23
  And then execute:
@@ -33,26 +35,47 @@ Here's an example of how to use the tool in your Ruby code:
33
35
  ```ruby
34
36
  require 'timex_datalink_caldav'
35
37
 
36
- client = TimexDatalinkCaldav::Client.new(your_username, your_password, your_server_uri, your_device)
37
- client.sync_to_watch
38
+ client = TimexDatalinkCaldav::Client.new(your_username, your_password, your_server_uri, your_device, your_protocol_version, days_forward)
39
+
40
+ client.parse_events
41
+ client.write_to_watch
38
42
  ```
39
43
 
40
44
  ### As a CLI Tool
41
45
 
42
- After installing the gem, you can use it as a CLI tool:
46
+ After installing the gem, you can use it as a CLI tool. You can specify the CalDAV server details directly on the command line:
43
47
 
44
48
  ```sh
45
- timex_datalink_caldav -u https://caldavendpoint.com -n your_username -p your_password -d your_device
49
+ timex_datalink_caldav -u https://caldavendpoint.com -n your_username -p your_password -d your_device -a your_protocol_version -f days_forward
46
50
  ```
47
51
 
48
- Please replace `your_username`, `your_password`, and `your_device` with your actual CalDAV server username, password, and device respectively. In this example, we're using Apple's iCloud CalDAV server. You'll need to replace this with the URI of your own CalDAV server if you're not using iCloud.
52
+ Please replace `https://caldavendpoint.com`, `your_username`, `your_password`, `your_device`, `your_protocol_version`, and `days_forward` with your actual CalDAV server URI, username, password, serial device, protocol version, and number of days to look forward for events, respectively.
53
+
54
+ Or you can provide these details in a configuration file:
55
+
56
+ ```sh
57
+ timex_datalink_caldav -c config.yml -a 1 -d /dev/tty.usbmodem0000000000001 -f 7
58
+ ```
59
+
60
+ The configuration file should be a YAML file in the following format:
61
+
62
+ ```yaml
63
+ endpoints:
64
+ - uri: https://www.google.com/calendar/dav/email@gmail.com/events
65
+ user: email@gmail.com
66
+ password: app_password
67
+ - uri: https://caldavendpoint2.com
68
+ user: your_username2
69
+ password: your_password2
70
+ - uri: https://icalendpoint.com/example.ics
71
+ ```
49
72
 
50
- ## Note
73
+ The device is a serial device that flashes an led when it receives data. On Linux, this is usually `/dev/tty*`. On macOS, this is usually `$(ls /dev/tty.usbmodem* | head -n 1)`. On Windows, this is usually `COM1`.
51
74
 
52
- Ensure you have the necessary dependencies installed on your system and you have the correct permissions to access the specified device.
75
+ If you want to use this, I highly recommend pairing it with the Raspberry Pi Pico and [this project](https://github.com/famiclone6502/DIY_Datalink_Adapter). It is the cheapest and easiest way to get a serial device that works with the Timex Datalink watch.
53
76
 
54
- The tool currently works with events that have attendees and converts event times to Eastern Standard Time (EST). Events are sorted by time before syncing to the watch.
77
+ ## Notes
55
78
 
56
- ## Resources
79
+ - This gem is not affiliated with Timex, nor is it affiliated with any CalDAV server. It is simply a tool that I wrote to sync my events from my CalDAV server to my Timex Datalink watch.
57
80
 
58
- If you want to use this I highly recommend pairing it with the Raspberry Pi Pico and [this project](https://github.com/famiclone6502/DIY_Datalink_Adapter).
81
+ - This gem uses the anniversary feature for full day events, and the appointments feature for events with a start and end time.
@@ -0,0 +1,7 @@
1
+ # Examples
2
+
3
+ This directory contains sample configurations and code for the timex_datalink_caldav gem.
4
+
5
+ ## Configuration
6
+
7
+ `caldav.yaml.sample` is a sample configuration file for CalDAV endpoints. You should rename it to `caldav.yaml` and adjust the URI, username, and password for your endpoints.
@@ -0,0 +1,8 @@
1
+ endpoints:
2
+ - uri: https://example.com/caldav1
3
+ user: user1
4
+ password: password1
5
+ - uri: https://example.com/caldav2
6
+ user: user2
7
+ password: password2
8
+ - uri: https://example.com/example.ical
@@ -1,15 +1,30 @@
1
1
  require 'calendav'
2
+ require 'icalendar'
2
3
  require 'icalendar/recurrence'
3
4
  require 'timex_datalink_client'
4
5
  require 'active_support/time'
6
+ require 'tzinfo'
5
7
 
6
8
  module TimexDatalinkCaldav
7
9
  class Client
8
- def initialize(user, password, server_url, serial_device)
10
+ def initialize(user, password, server_url, serial_device, protocol_version, days_forward = 1)
9
11
  @user = user
10
12
  @password = password
11
13
  @server_url = server_url
12
14
  @serial_device = serial_device
15
+ @days_forward = days_forward
16
+ @protocol_version = protocol_version.to_i
17
+ @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
25
+
26
+ def get_localzone
27
+ TZInfo::Timezone.get(TZInfo::Timezone.all_country_zones.detect {|z| z.period_for_local(Time.now).utc_total_offset == Time.now.utc_offset}.identifier)
13
28
  end
14
29
 
15
30
  def get_events
@@ -20,68 +35,130 @@ module TimexDatalinkCaldav
20
35
  authentication: :basic_auth
21
36
  )
22
37
 
23
- # Create a new client with the credentials
24
- client = Calendav.client(credentials)
38
+ if @user && @password
39
+ # Create a new client with the credentials
40
+ client = Calendav.client(credentials)
41
+
42
+ # Get events from the calendar for the next day
43
+ caldav_events = client.events.list(@server_url, from: Time.now, to: Time.now + @days_forward*24*60*60)
44
+ events = caldav_events.map { |event| Icalendar::Event.parse(event.calendar_data).first }
45
+ else
46
+ cal_file = URI.open(@server_url)
47
+ cals = Icalendar::Calendar.parse(cal_file)
48
+ cal = cals.first
49
+ events = cal.events
50
+ end
25
51
 
26
- # Get events from the calendar for the next day
27
- client.events.list(@server_url, from: Time.now, to: Time.now + 24*60*60)
52
+ events
28
53
  end
29
54
 
30
- def sync_to_watch
55
+ def parse_events
31
56
  events = get_events
32
-
57
+
33
58
  appointments = []
59
+ anniversaries = []
34
60
  appointment_map = {} # Used to avoid duplicate appointments
35
-
61
+ anniversary_map = {} # Used to avoid duplicate anniversaries
62
+
36
63
  events.each do |event|
37
- ical_events = Icalendar::Event.parse(event.calendar_data)
38
- if ical_events.any?
39
- ical_event = ical_events.first
40
- if ical_event.attendee&.any? # Exclude events without attendees
41
- next_occurrence = ical_event.occurrences_between(Time.now, Time.now + 24*60*60).first
42
- if next_occurrence
43
- est_time = next_occurrence.start_time.in_time_zone('Eastern Time (US & Canada)')
44
- key = "#{est_time}_#{ical_event.summary.to_s}"
45
- unless appointment_map[key] # Check if the event is already in the map
46
- puts "Adding appointment: #{ical_event.summary.to_s} at time #{est_time}"
47
- appointment = TimexDatalinkClient::Protocol1::Eeprom::Appointment.new(
48
- time: est_time,
49
- message: ical_event.summary.to_s
50
- )
51
- appointments << appointment
52
- appointment_map[key] = true
53
- end
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
81
+ 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
54
95
  end
55
96
  end
56
97
  end
57
98
  end
58
99
 
59
- # Sort the appointments by time
60
- appointments.sort_by! { |appointment| appointment.time }
61
- write_to_watch(appointments)
100
+ [appointments, anniversaries]
62
101
  end
63
102
 
64
- def write_to_watch(appointments)
65
- time1 = Time.now
66
-
67
- models = [
68
- TimexDatalinkClient::Protocol1::Sync.new,
69
- TimexDatalinkClient::Protocol1::Start.new,
70
- TimexDatalinkClient::Protocol1::Time.new(
103
+ def write_to_watch(appointments, anniversaries)
104
+ appointments.sort_by! { |appointment| appointment.time }
105
+ # add 3 because it always seems to be about 3 seconds behind.
106
+ time1 = Time.now + 3
107
+ time2 = time1.dup.utc
108
+
109
+ if @protocol_version == 1
110
+ time_model = @protocol_class::Time.new(
71
111
  zone: 1,
72
112
  time: time1,
73
113
  is_24h: false
74
- ),
75
- TimexDatalinkClient::Protocol1::TimeName.new(
114
+ )
115
+ time_name_model = @protocol_class::TimeName.new(
76
116
  zone: 1,
77
117
  name: time1.zone
78
- ),
79
- TimexDatalinkClient::Protocol1::Eeprom.new(
118
+ )
119
+ utc_time_model = @protocol_class::Time.new(
120
+ zone: 2,
121
+ time: time2,
122
+ is_24h: true
123
+ )
124
+ utc_time_name_model = @protocol_class::TimeName.new(
125
+ zone: 2,
126
+ name: time2.zone
127
+ )
128
+ else
129
+ time_model = @protocol_class::Time.new(
130
+ zone: 1,
131
+ name: time1.zone,
132
+ time: time1,
133
+ is_24h: false,
134
+ date_format: "%_m-%d-%y"
135
+ )
136
+ utc_time_model = @protocol_class::Time.new(
137
+ zone: 2,
138
+ name: "UTC",
139
+ time: time2,
140
+ is_24h: true,
141
+ date_format: "%y-%m-%d"
142
+ )
143
+ time_name_model = nil # Not needed for protocol version 3 and 4
144
+ utc_time_name_model = nil # Not needed for protocol version 3 and 4
145
+
146
+ 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(
80
156
  appointments: appointments,
157
+ anniversaries: anniversaries,
81
158
  appointment_notification_minutes: 5
82
159
  ),
83
- TimexDatalinkClient::Protocol1::End.new
84
- ]
160
+ @protocol_class::End.new
161
+ ].compact # Remove any nil entries
85
162
 
86
163
  timex_datalink_client = TimexDatalinkClient.new(
87
164
  serial_device: @serial_device,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TimexDatalinkCaldav
4
- VERSION = "0.2.0"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
1
  require 'optparse'
3
-
2
+ require 'yaml'
3
+ require 'open-uri'
4
4
  require_relative "timex_datalink_caldav/client"
5
5
 
6
6
  module TimexDatalinkCaldav
@@ -10,34 +10,72 @@ module TimexDatalinkCaldav
10
10
  end
11
11
 
12
12
  def execute
13
- client = TimexDatalinkCaldav::Client.new(@options.fetch(:user), @options.fetch(:password), @options.fetch(:uri), @options.fetch(:device))
14
- client.sync_to_watch
13
+ all_appointments = []
14
+ all_anniversaries = []
15
+
16
+ @options[:endpoints].each do |endpoint|
17
+ client = TimexDatalinkCaldav::Client.new(endpoint[:user], endpoint[:password], endpoint[:uri], @options[:device], @options[:api], @options[:days_forward])
18
+ appointments, anniversaries = client.parse_events
19
+ all_appointments.concat(appointments) if appointments.any?
20
+ all_anniversaries.concat(anniversaries) if anniversaries.any?
21
+ end
22
+
23
+ if all_appointments.any? || all_anniversaries.any?
24
+ client = TimexDatalinkCaldav::Client.new(
25
+ @options[:endpoints][0][:user],
26
+ @options[:endpoints][0][:password],
27
+ @options[:endpoints][0][:uri],
28
+ @options[:device],
29
+ @options[:api],
30
+ @options[:days_forward]
31
+ )
32
+ client.write_to_watch(all_appointments, all_anniversaries)
33
+ end
15
34
  end
16
35
 
17
36
  private
18
37
 
19
38
  def parse_options(arguments)
20
- options = {}
39
+ options = { days_forward: 1 }
40
+ cli_endpoint = {}
21
41
  OptionParser.new do |opts|
22
42
  opts.banner = "Usage: timex_datalink_caldav [options]"
23
43
 
44
+ opts.on("-c", "--config FILE", "Configuration file") do |v|
45
+ raise ArgumentError, "Both CLI options and configuration file provided. Please provide only one." if cli_endpoint.any?
46
+ parsed_config = YAML.load_file(v)
47
+ options[:endpoints] = parsed_config['endpoints'].map do |endpoint|
48
+ endpoint.each_with_object({}) { |(k, v), result| result[k.to_sym] = v }
49
+ end
50
+ end
51
+
24
52
  opts.on("-u", "--uri URI", "CalDAV server URI") do |v|
25
- options[:uri] = v
53
+ cli_endpoint[:uri] = v
26
54
  end
27
55
 
28
56
  opts.on("-n", "--user USERNAME", "Username for CalDAV server") do |v|
29
- options[:user] = v
57
+ cli_endpoint[:user] = v
30
58
  end
31
59
 
32
60
  opts.on("-p", "--password PASSWORD", "Password for CalDAV server") do |v|
33
- options[:password] = v
61
+ cli_endpoint[:password] = v
34
62
  end
35
63
 
36
64
  opts.on("-d", "--device DEVICE", "Serial device for Timex Datalink watch") do |v|
37
65
  options[:device] = v
38
66
  end
67
+
68
+ opts.on("-a", "--api PROTOCOL_VERSION", "Protocol Version") do |v|
69
+ options[:api] = v
70
+ end
71
+
72
+ opts.on("-f", "--forward DAYS", Integer, "Number of days to look forward for events") do |v|
73
+ options[:days_forward] = v
74
+ end
39
75
  end.parse!(arguments)
76
+
77
+ options[:endpoints] = [cli_endpoint] if cli_endpoint.any? and options[:endpoints].nil?
40
78
  options
41
79
  end
42
80
  end
43
- end
81
+ end
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: 0.2.0
4
+ version: 1.0.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-15 00:00:00.000000000 Z
11
+ date: 2023-05-19 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.'
@@ -19,13 +19,15 @@ executables:
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
- - ".DS_Store"
23
22
  - CHANGELOG.md
24
23
  - Gemfile
25
24
  - Gemfile.lock
25
+ - LICENSE
26
26
  - README.md
27
27
  - Rakefile
28
28
  - bin/timex_datalink_caldav
29
+ - examples/README.md
30
+ - examples/caldav.yaml.sample
29
31
  - lib/timex_datalink_caldav.rb
30
32
  - lib/timex_datalink_caldav/client.rb
31
33
  - lib/timex_datalink_caldav/version.rb
data/.DS_Store DELETED
Binary file