mhc 1.0.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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/COPYRIGHT +28 -0
  6. data/Gemfile +8 -0
  7. data/README.org +209 -0
  8. data/Rakefile +13 -0
  9. data/bin/mhc +312 -0
  10. data/emacs/Cask +25 -0
  11. data/emacs/Makefile +58 -0
  12. data/emacs/mhc-calendar.el +1723 -0
  13. data/emacs/mhc-calfw.el +135 -0
  14. data/emacs/mhc-compat.el +90 -0
  15. data/emacs/mhc-date.el +642 -0
  16. data/emacs/mhc-day.el +149 -0
  17. data/emacs/mhc-db.el +158 -0
  18. data/emacs/mhc-draft.el +211 -0
  19. data/emacs/mhc-e21.el +167 -0
  20. data/emacs/mhc-face.el +236 -0
  21. data/emacs/mhc-file.el +224 -0
  22. data/emacs/mhc-guess.el +648 -0
  23. data/emacs/mhc-header.el +176 -0
  24. data/emacs/mhc-logic.el +563 -0
  25. data/emacs/mhc-message.el +130 -0
  26. data/emacs/mhc-minibuf.el +466 -0
  27. data/emacs/mhc-misc.el +248 -0
  28. data/emacs/mhc-mua.el +260 -0
  29. data/emacs/mhc-parse.el +286 -0
  30. data/emacs/mhc-process.el +35 -0
  31. data/emacs/mhc-ps.el +1174 -0
  32. data/emacs/mhc-record.el +201 -0
  33. data/emacs/mhc-schedule.el +202 -0
  34. data/emacs/mhc-summary.el +763 -0
  35. data/emacs/mhc-sync.el +158 -0
  36. data/emacs/mhc-vars.el +149 -0
  37. data/emacs/mhc.el +1114 -0
  38. data/icons/Anniversary.xbm +6 -0
  39. data/icons/Anniversary.xpm +27 -0
  40. data/icons/Birthday.xbm +6 -0
  41. data/icons/Birthday.xpm +25 -0
  42. data/icons/Business.xbm +6 -0
  43. data/icons/Business.xpm +24 -0
  44. data/icons/CheckBox.xbm +6 -0
  45. data/icons/CheckBox.xpm +24 -0
  46. data/icons/CheckedBox.xbm +6 -0
  47. data/icons/CheckedBox.xpm +25 -0
  48. data/icons/Conflict.xbm +6 -0
  49. data/icons/Conflict.xpm +22 -0
  50. data/icons/Date.xbm +6 -0
  51. data/icons/Date.xpm +29 -0
  52. data/icons/Holiday.xbm +6 -0
  53. data/icons/Holiday.xpm +25 -0
  54. data/icons/Link.xbm +6 -0
  55. data/icons/Link.xpm +25 -0
  56. data/icons/Other.xbm +6 -0
  57. data/icons/Other.xpm +28 -0
  58. data/icons/Party.xbm +6 -0
  59. data/icons/Party.xpm +23 -0
  60. data/icons/Private.xbm +6 -0
  61. data/icons/Private.xpm +26 -0
  62. data/icons/Recurrence.xbm +6 -0
  63. data/icons/Recurrence.xpm +98 -0
  64. data/icons/Vacation.xbm +6 -0
  65. data/icons/Vacation.xpm +26 -0
  66. data/lib/mhc.rb +45 -0
  67. data/lib/mhc/builder.rb +64 -0
  68. data/lib/mhc/caldav.rb +304 -0
  69. data/lib/mhc/calendar.rb +106 -0
  70. data/lib/mhc/command.rb +13 -0
  71. data/lib/mhc/command/cache.rb +14 -0
  72. data/lib/mhc/command/completions.rb +108 -0
  73. data/lib/mhc/command/init.rb +133 -0
  74. data/lib/mhc/command/scan.rb +33 -0
  75. data/lib/mhc/command/sync.rb +22 -0
  76. data/lib/mhc/config.rb +229 -0
  77. data/lib/mhc/converter.rb +330 -0
  78. data/lib/mhc/datastore.rb +164 -0
  79. data/lib/mhc/date_enumerator.rb +274 -0
  80. data/lib/mhc/date_frame.rb +124 -0
  81. data/lib/mhc/date_helper.rb +49 -0
  82. data/lib/mhc/etag.rb +68 -0
  83. data/lib/mhc/event.rb +396 -0
  84. data/lib/mhc/formatter.rb +312 -0
  85. data/lib/mhc/logger.rb +94 -0
  86. data/lib/mhc/modifier.rb +149 -0
  87. data/lib/mhc/occurrence.rb +94 -0
  88. data/lib/mhc/occurrence_enumerator.rb +113 -0
  89. data/lib/mhc/property_value.rb +33 -0
  90. data/lib/mhc/property_value/date.rb +190 -0
  91. data/lib/mhc/property_value/integer.rb +15 -0
  92. data/lib/mhc/property_value/list.rb +41 -0
  93. data/lib/mhc/property_value/period.rb +49 -0
  94. data/lib/mhc/property_value/range.rb +100 -0
  95. data/lib/mhc/property_value/recurrence_condition.rb +272 -0
  96. data/lib/mhc/property_value/text.rb +11 -0
  97. data/lib/mhc/property_value/time.rb +45 -0
  98. data/lib/mhc/query.rb +210 -0
  99. data/lib/mhc/sync.rb +46 -0
  100. data/lib/mhc/sync/driver.rb +108 -0
  101. data/lib/mhc/sync/status.rb +70 -0
  102. data/lib/mhc/sync/status_manager.rb +142 -0
  103. data/lib/mhc/sync/strategy.rb +233 -0
  104. data/lib/mhc/sync/syncinfo.rb +98 -0
  105. data/lib/mhc/templates/config.yml.erb +142 -0
  106. data/lib/mhc/version.rb +4 -0
  107. data/lib/mhc/webdav.rb +319 -0
  108. data/mhc.gemspec +24 -0
  109. data/samples/DOT.mhc-config.yml +116 -0
  110. data/samples/japanese-holidays.mhcc +153 -0
  111. data/samples/mhc-completions.zsh +11 -0
  112. data/spec/mhc_spec.rb +682 -0
  113. data/spec/spec_helper.rb +9 -0
  114. data/xpm/close.xpm +18 -0
  115. data/xpm/delete.xpm +19 -0
  116. data/xpm/exit.xpm +18 -0
  117. data/xpm/month.xpm +18 -0
  118. data/xpm/next.xpm +18 -0
  119. data/xpm/next2.xpm +18 -0
  120. data/xpm/next_year.xpm +18 -0
  121. data/xpm/open.xpm +19 -0
  122. data/xpm/prev.xpm +18 -0
  123. data/xpm/prev2.xpm +18 -0
  124. data/xpm/prev_year.xpm +18 -0
  125. data/xpm/save.xpm +19 -0
  126. data/xpm/today.xpm +18 -0
  127. metadata +214 -0
@@ -0,0 +1,6 @@
1
+ #define Vacation_width 16
2
+ #define Vacation_height 16
3
+ static unsigned char Vacation_bits[] = {
4
+ 0x00, 0x00, 0x00, 0x3f, 0x80, 0x20, 0x80, 0x3f, 0x00, 0x12, 0x00, 0x3f,
5
+ 0x80, 0x2c, 0x40, 0x2c, 0x20, 0x2c, 0xf8, 0x7f, 0xfc, 0x7f, 0xfa, 0x7f,
6
+ 0xfe, 0x7f, 0xee, 0x6f, 0x38, 0x38, 0x00, 0x00, };
@@ -0,0 +1,26 @@
1
+ /* XPM */
2
+ static char * Vacation_xpm[] = {
3
+ "16 16 7 1",
4
+ " s backgroundColor c None",
5
+ ". c #FFFFFFFFFFFF",
6
+ "X c #000000000000",
7
+ "o c #DF7DDF7DDF7D",
8
+ "O c #0000FFFFFFFF",
9
+ "+ c #BEFBBEFBBEFB",
10
+ "@ c #FFFFF7DE8617",
11
+ " ...... ",
12
+ " .XXXXXX. ",
13
+ " .XoooooX. ",
14
+ " .XXXXXXX. ",
15
+ " ..X..X. ",
16
+ " .XXXXXX. ",
17
+ " .XOO++OX. ",
18
+ " .XOOO++OX. ",
19
+ " ..XOOOO++OX. ",
20
+ " .XXXXXXXXXXXX.",
21
+ " .XXXXXXXXXXXXX.",
22
+ ".X@XXXXXXXXXXXX.",
23
+ ".XXXXXXXXXXXXXX.",
24
+ ".XXXoXXXXXXXoXX.",
25
+ " ..XXX.....XXX. ",
26
+ " ... ... "};
data/lib/mhc.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'tzinfo'
2
+ require 'ri_cal'
3
+ require "kconv"
4
+
5
+ module Mhc # :nodoc:
6
+ def self.default_tzid=(tzid)
7
+ @tzid = tzid
8
+ ENV["MHC_TZID"] = tzid
9
+ RiCal::PropertyValue::DateTime.default_tzid = tzid
10
+ end
11
+
12
+ def self.default_tzid
13
+ @tzid
14
+ end
15
+
16
+ if ENV["MHC_TZID"]
17
+ self.default_tzid = ENV["MHC_TZID"]
18
+ end
19
+
20
+ class ConfigurationError < StandardError ; end
21
+
22
+ dir = File.dirname(__FILE__) + "/mhc"
23
+
24
+ autoload :Builder, "#{dir}/builder.rb"
25
+ autoload :CalDav, "#{dir}/caldav.rb"
26
+ autoload :Calendar, "#{dir}/calendar.rb"
27
+ autoload :Command, "#{dir}/command.rb"
28
+ autoload :Config, "#{dir}/config.rb"
29
+ autoload :Converter, "#{dir}/converter.rb"
30
+ autoload :DataStore, "#{dir}/datastore.rb"
31
+ autoload :DateEnumerator, "#{dir}/date_enumerator.rb"
32
+ autoload :DateFrame, "#{dir}/date_frame.rb"
33
+ autoload :DateHelper, "#{dir}/date_helper.rb"
34
+ autoload :EtagStore, "#{dir}/etag.rb"
35
+ autoload :Event, "#{dir}/event.rb"
36
+ autoload :Formatter, "#{dir}/formatter.rb"
37
+ autoload :Logger, "#{dir}/logger.rb"
38
+ autoload :Modifier, "#{dir}/modifier.rb"
39
+ autoload :Occurrence, "#{dir}/occurrence.rb"
40
+ autoload :OccurrenceEnumerator, "#{dir}/occurrence_enumerator.rb"
41
+ autoload :PropertyValue, "#{dir}/property_value.rb"
42
+ autoload :Query, "#{dir}/query.rb"
43
+ autoload :Sync, "#{dir}/sync.rb"
44
+ autoload :Version, "#{dir}/version.rb"
45
+ end
@@ -0,0 +1,64 @@
1
+ require "yaml"
2
+ require "uri"
3
+
4
+ ################################################################
5
+ ## load config
6
+
7
+ module Mhc
8
+ class Builder
9
+ def initialize(config)
10
+ @config = config
11
+ @config = Mhc::Config.create_from_file(config) if config.is_a?(String)
12
+ end
13
+
14
+ def calendar(calendar_name)
15
+ calendar = @config.calendars[calendar_name]
16
+ raise Mhc::ConfigurationError, "calendar '#{calendar_name}' not found" unless calendar
17
+
18
+ case calendar.type
19
+ when "caldav"
20
+ db = Mhc::CalDav::Client.new(calendar.url).set_basic_auth(calendar.user, calendar.password)
21
+ when "directory"
22
+ db = Mhc::CalDav::Cache.new(calendar.top_directory)
23
+ when "lastnote"
24
+ db = Mhc::LastNote::Client.new(calendar.name)
25
+ when "mhc"
26
+ db = Mhc::Calendar.new(Mhc::DataStore.new(@config.general.repository), calendar.modifiers, &calendar.filter)
27
+ end
28
+ return db
29
+ end
30
+
31
+ def sync_driver(channel_name)
32
+ channel = @config.sync_channels[channel_name]
33
+ raise Mhc::ConfigurationError, "sync channel '#{channel_name}' not found" unless channel
34
+
35
+ directory1, directory2 = cache_directory_pair(channel)
36
+ strategy = channel.strategy.to_s.downcase.to_sym
37
+
38
+ db1 = calendar_with_etag_track(calendar(channel.calendar1), directory1)
39
+ db2 = calendar_with_etag_track(calendar(channel.calendar2), directory2)
40
+
41
+ return Mhc::Sync::Driver.new(db1, db2, strategy)
42
+ end
43
+
44
+ ################################################################
45
+ private
46
+
47
+ def calendar_with_etag_track(calendar, etag_store_directory)
48
+ etag_db = Mhc::EtagStore.new(etag_store_directory)
49
+ return Mhc::Sync::StatusManager.new(calendar, etag_db)
50
+ end
51
+
52
+ def cache_directory_pair(channel, top_directory = File.expand_path("status/sync_channels", @config.general.repository))
53
+ base = File.expand_path(channel.name, top_directory)
54
+
55
+ directory1 = File.join(base, channel.calendar1)
56
+ directory2 = File.join(base, channel.calendar2)
57
+
58
+ FileUtils.mkdir_p(directory1) unless FileTest.exist?(directory1)
59
+ FileUtils.mkdir_p(directory2) unless FileTest.exist?(directory2)
60
+
61
+ return [directory1, directory2]
62
+ end
63
+ end # class Builder
64
+ end # module Mhc
data/lib/mhc/caldav.rb ADDED
@@ -0,0 +1,304 @@
1
+ require File.dirname(__FILE__) + '/webdav'
2
+
3
+ module Net
4
+ class HTTP
5
+ class Report < HTTPRequest
6
+ METHOD = 'REPORT'
7
+ REQUEST_HAS_BODY = true
8
+ RESPONSE_HAS_BODY = true
9
+ end
10
+ end
11
+ end
12
+
13
+ module Mhc
14
+ class CalDav
15
+ class Report
16
+ attr_accessor :uid, :etag, :href, :content_type, :status, :ics
17
+
18
+ def self.parse(xmldoc)
19
+ info = self.new
20
+
21
+ href, status, content_type, etag, ics =
22
+ %w(D:href
23
+ D:propstat/D:status
24
+ D:propstat/D:prop/D:getcontenttype
25
+ D:propstat/D:prop/D:getetag
26
+ D:propstat/D:prop/caldav:calendar-data
27
+ ).map{|e| xmldoc.elements[e].text rescue nil}
28
+
29
+ info.href = URI.unescape(href)
30
+ info.uid = File.basename(info.href, ".ics")
31
+ info.status = status
32
+ info.content_type = content_type
33
+ info.etag = etag # unquote_string(etag)
34
+ info.ics = ics
35
+ return info
36
+ end
37
+
38
+ private_class_method
39
+
40
+ def self.unquote_string(str)
41
+ return str.gsub('"', "")
42
+ end
43
+
44
+ end # class Report
45
+
46
+ class CalendarProperty
47
+ attr_accessor :description, :color, :displayname, :ctag
48
+
49
+ def initialize(description, color, displayname, ctag)
50
+ @description, @color, @displayname, @ctag = description, color, displayname, ctag
51
+ end
52
+
53
+ def self.parse(xml)
54
+ xml = REXML::Document.new(xml) if xml.is_a?(String)
55
+ description, color, displayname, ctag =
56
+ %w(caldav:calendar-description
57
+ ical:calendar-color
58
+ D:displayname
59
+ cs:getctag
60
+ ).map{|e| xml.elements[e].text rescue nil}
61
+ self.new(description, color, displayname, ctag)
62
+ end
63
+ end
64
+
65
+ class ReportCollection
66
+ def initialize
67
+ @db = {}
68
+ @calendar_property = nil
69
+ end
70
+
71
+ def collection
72
+ return @db
73
+ end
74
+
75
+ def find(uid)
76
+ return @db[uid]
77
+ end
78
+
79
+ def uid_list
80
+ return @db.keys
81
+ end
82
+
83
+ def update(info)
84
+ @db[info.uid] = info
85
+ end
86
+
87
+ def calendar_property
88
+ @calendar_property
89
+ end
90
+
91
+ def set_calendar_property(xml)
92
+ @calendar_property = CalendarProperty.parse(xml)
93
+ end
94
+
95
+ def self.parse(xml)
96
+ db = self.new
97
+ xml = REXML::Document.new(xml) if xml.is_a?(String)
98
+
99
+ xml.elements.each("D:multistatus/D:response") do |res|
100
+ if res.elements["D:propstat/D:prop/D:resourcetype/D:collection"]
101
+ db.set_calendar_property(res.elements["D:propstat/D:prop"])
102
+ else
103
+ db.update(Report.parse(res))
104
+ end
105
+ end
106
+ return db
107
+ end
108
+ end # class ReportCollection
109
+
110
+
111
+ class Client < WebDav::Client
112
+
113
+ def report(xml, path = @top_directory, depth = 1)
114
+ req = setup_request(Net::HTTP::Report, path)
115
+ req['Depth'] = depth
116
+ req.content_length = xml.size
117
+ req.content_type = 'application/xml; charset="utf-8"'
118
+ req.body = xml
119
+ res = @http.request(req)
120
+ #check_status_code(res, 207)
121
+ return res
122
+ end
123
+
124
+ ## for caldav sync
125
+ ## etag_report is one of:
126
+ ## + {uid_string => etag_object } style hash,
127
+ ## + {uid_string => etag_string } style hash,
128
+ ## + [uid_etag_object] style array,
129
+ ## uid_etag_object is an object which respond to #etag and #uid method.
130
+ ## etag_object is an object which respond to #etag method.
131
+ def report_etags(uids = nil) # XXX: handle uids
132
+ ReportCollection.parse(self.propfind.body).collection
133
+ # FIXME: I want to support schedule-tag (RFC6638) in the future,
134
+ # but we need to prepare a right path to migrate from etag-db.
135
+ # xml = <<-EOS
136
+ # <A:propfind xmlns:A="DAV:" xmlns:B="urn:ietf:params:xml:ns:caldav">
137
+ # <A:prop>
138
+ # <B:schedule-tag/>
139
+ # <A:getetag/>
140
+ # </A:prop>
141
+ # </A:propfind>
142
+ # EOS
143
+ # ReportCollection.parse(self.propfind(@top_directory, 1, xml).body).collection
144
+ end
145
+
146
+ # for caldav sync
147
+ def get_with_etag(uid_or_href)
148
+ res = get(uid_or_href)
149
+ return [res, res['etag']]
150
+ end
151
+
152
+ # for caldav sync
153
+ # return value : false ... failed.
154
+ # return value : true ... successful but etag is not available
155
+ # return value : String ... successful with new etag
156
+ def put_if_match(uid, ics_string, etag)
157
+ STDERR.print "CALDAV put_if_match :uid => #{uid}"
158
+ STDERR.print ", :etag => #{etag}" if etag
159
+ STDERR.print "... "
160
+ begin
161
+ res = put(ics_string, uid, etag)
162
+ rescue Exception => e
163
+ STDERR.print "failed: (#{e.to_s})\n"
164
+ return false
165
+ end
166
+ STDERR.print "succeeded #{res['etag']}\n"
167
+ return res['etag'] || true
168
+ end
169
+
170
+ def delete_if_match(uid, etag)
171
+ begin
172
+ res = delete(uid, etag)
173
+ rescue Exception => e
174
+ return false
175
+ end
176
+ return true
177
+ end
178
+
179
+ def delete(uid_or_href, ifmatch = nil)
180
+ super(adjust_path(uid_or_href))
181
+ end
182
+
183
+ def get(uid_or_href)
184
+ super(adjust_path(uid_or_href))
185
+ end
186
+
187
+ def head(uid_or_href)
188
+ super(adjust_path(uid_or_href))
189
+ end
190
+
191
+ def put(content, uid_or_href, ifmatch = nil)
192
+ super(content, adjust_path(uid_or_href), ifmatch)
193
+ end
194
+
195
+ def report_calendar_multiget(href_list, path = @top_directory)
196
+ xml = '<?xml version="1.0" encoding="utf-8" ?>'
197
+ xml += <<-EOS
198
+ <C:calendar-multiget xmlns:D="DAV:"
199
+ xmlns:C="urn:ietf:params:xml:ns:caldav">
200
+ <D:prop>
201
+ <D:getetag/>
202
+ <C:calendar-data/>
203
+ </D:prop>
204
+ #{href_list.map{|href| "<D:href>" + href + "</D:href>\n"}}
205
+ </C:calendar-multiget>
206
+ EOS
207
+ return ReportCollection.parse(report(xml, path).body)
208
+ end
209
+
210
+ def fetch_calendar_list(url, username, userpass)
211
+ depth = 1
212
+ split_url = URI.split(url)
213
+ host_url = split_url[0] + "://" + split_url[2]
214
+ body = <<-EOF_BODY
215
+ <?xml version="1.0" encoding="UTF-8"?>
216
+ <A:propfind xmlns:A="DAV:">
217
+ <A:prop>
218
+ <A:add-member/>
219
+ <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
220
+ <D:autoprovisioned xmlns:D="http://apple.com/ns/ical/"/>
221
+ <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
222
+ <D:calendar-color xmlns:D="http://apple.com/ns/ical/"/>
223
+ <B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav"/>
224
+ <B:calendar-free-busy-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
225
+ <D:calendar-order xmlns:D="http://apple.com/ns/ical/"/>
226
+ <B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav"/>
227
+ <A:current-user-privilege-set/>
228
+ <B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav"/>
229
+ <B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav"/>
230
+ <A:displayname/>
231
+ <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
232
+ <D:language-code xmlns:D="http://apple.com/ns/ical/"/>
233
+ <D:location-code xmlns:D="http://apple.com/ns/ical/"/>
234
+ <A:owner/>
235
+ <C:pre-publish-url xmlns:C="http://calendarserver.org/ns/"/>
236
+ <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
237
+ <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
238
+ <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
239
+ <A:quota-available-bytes/>
240
+ <A:quota-used-bytes/>
241
+ <D:refreshrate xmlns:D="http://apple.com/ns/ical/"/>
242
+ <A:resource-id/>
243
+ <A:resourcetype/>
244
+ <B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"/>
245
+ <B:schedule-default-calendar-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
246
+ <C:source xmlns:C="http://calendarserver.org/ns/"/>
247
+ <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
248
+ <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
249
+ <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
250
+ <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
251
+ <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
252
+ <A:supported-report-set/>
253
+ <A:sync-token/>
254
+ </A:prop>
255
+ </A:propfind>
256
+ EOF_BODY
257
+ res = self.propfind(url, depth, body)
258
+ return [] if (res.code.to_i / 200) != 1
259
+
260
+ xml = Nokogiri::XML(res.body).remove_namespaces!
261
+ blocks = xml.xpath('//multistatus/response')
262
+
263
+ calendars = []
264
+
265
+ blocks.each do |block|
266
+ if block.xpath('propstat/prop/calendar-color')[0].content != ""
267
+ href = block.xpath('href')[0].content
268
+ displayname = block.xpath('propstat/prop/displayname')[0].content
269
+ color = block.xpath('propstat/prop/calendar-color')[0].content
270
+ if color =~ /^#(..)(..)(..)/
271
+ color = "#" + $1 + $2 + $3
272
+ # color = double_lightness_of_hexrgb(color)
273
+ end
274
+ description = block.xpath('propstat/prop/calendar-description')[0].content
275
+ calendars << {"url" => host_url + href.to_s, "displayname" => displayname.to_s,
276
+ "color" => color.to_s, "description" => description.to_s }
277
+ end
278
+ end
279
+
280
+ return calendars
281
+ end
282
+
283
+ private
284
+ def adjust_path(uid_or_href) # XXX: google calendar specific?
285
+ if uid_or_href =~ /^\//
286
+ return uid_or_href
287
+ else
288
+ return File.expand_path(uid_or_href, @top_directory)
289
+ end
290
+ end
291
+
292
+ end # class Client
293
+
294
+ class Cache < WebDav::Cache
295
+ def report(xml, depth = 1)
296
+ raise NotImplementedError
297
+ end
298
+
299
+ def report_calendar_multiget(path_list)
300
+ raise NotImplementedError
301
+ end
302
+ end # class Cache
303
+ end
304
+ end