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.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/COPYRIGHT +28 -0
- data/Gemfile +8 -0
- data/README.org +209 -0
- data/Rakefile +13 -0
- data/bin/mhc +312 -0
- data/emacs/Cask +25 -0
- data/emacs/Makefile +58 -0
- data/emacs/mhc-calendar.el +1723 -0
- data/emacs/mhc-calfw.el +135 -0
- data/emacs/mhc-compat.el +90 -0
- data/emacs/mhc-date.el +642 -0
- data/emacs/mhc-day.el +149 -0
- data/emacs/mhc-db.el +158 -0
- data/emacs/mhc-draft.el +211 -0
- data/emacs/mhc-e21.el +167 -0
- data/emacs/mhc-face.el +236 -0
- data/emacs/mhc-file.el +224 -0
- data/emacs/mhc-guess.el +648 -0
- data/emacs/mhc-header.el +176 -0
- data/emacs/mhc-logic.el +563 -0
- data/emacs/mhc-message.el +130 -0
- data/emacs/mhc-minibuf.el +466 -0
- data/emacs/mhc-misc.el +248 -0
- data/emacs/mhc-mua.el +260 -0
- data/emacs/mhc-parse.el +286 -0
- data/emacs/mhc-process.el +35 -0
- data/emacs/mhc-ps.el +1174 -0
- data/emacs/mhc-record.el +201 -0
- data/emacs/mhc-schedule.el +202 -0
- data/emacs/mhc-summary.el +763 -0
- data/emacs/mhc-sync.el +158 -0
- data/emacs/mhc-vars.el +149 -0
- data/emacs/mhc.el +1114 -0
- data/icons/Anniversary.xbm +6 -0
- data/icons/Anniversary.xpm +27 -0
- data/icons/Birthday.xbm +6 -0
- data/icons/Birthday.xpm +25 -0
- data/icons/Business.xbm +6 -0
- data/icons/Business.xpm +24 -0
- data/icons/CheckBox.xbm +6 -0
- data/icons/CheckBox.xpm +24 -0
- data/icons/CheckedBox.xbm +6 -0
- data/icons/CheckedBox.xpm +25 -0
- data/icons/Conflict.xbm +6 -0
- data/icons/Conflict.xpm +22 -0
- data/icons/Date.xbm +6 -0
- data/icons/Date.xpm +29 -0
- data/icons/Holiday.xbm +6 -0
- data/icons/Holiday.xpm +25 -0
- data/icons/Link.xbm +6 -0
- data/icons/Link.xpm +25 -0
- data/icons/Other.xbm +6 -0
- data/icons/Other.xpm +28 -0
- data/icons/Party.xbm +6 -0
- data/icons/Party.xpm +23 -0
- data/icons/Private.xbm +6 -0
- data/icons/Private.xpm +26 -0
- data/icons/Recurrence.xbm +6 -0
- data/icons/Recurrence.xpm +98 -0
- data/icons/Vacation.xbm +6 -0
- data/icons/Vacation.xpm +26 -0
- data/lib/mhc.rb +45 -0
- data/lib/mhc/builder.rb +64 -0
- data/lib/mhc/caldav.rb +304 -0
- data/lib/mhc/calendar.rb +106 -0
- data/lib/mhc/command.rb +13 -0
- data/lib/mhc/command/cache.rb +14 -0
- data/lib/mhc/command/completions.rb +108 -0
- data/lib/mhc/command/init.rb +133 -0
- data/lib/mhc/command/scan.rb +33 -0
- data/lib/mhc/command/sync.rb +22 -0
- data/lib/mhc/config.rb +229 -0
- data/lib/mhc/converter.rb +330 -0
- data/lib/mhc/datastore.rb +164 -0
- data/lib/mhc/date_enumerator.rb +274 -0
- data/lib/mhc/date_frame.rb +124 -0
- data/lib/mhc/date_helper.rb +49 -0
- data/lib/mhc/etag.rb +68 -0
- data/lib/mhc/event.rb +396 -0
- data/lib/mhc/formatter.rb +312 -0
- data/lib/mhc/logger.rb +94 -0
- data/lib/mhc/modifier.rb +149 -0
- data/lib/mhc/occurrence.rb +94 -0
- data/lib/mhc/occurrence_enumerator.rb +113 -0
- data/lib/mhc/property_value.rb +33 -0
- data/lib/mhc/property_value/date.rb +190 -0
- data/lib/mhc/property_value/integer.rb +15 -0
- data/lib/mhc/property_value/list.rb +41 -0
- data/lib/mhc/property_value/period.rb +49 -0
- data/lib/mhc/property_value/range.rb +100 -0
- data/lib/mhc/property_value/recurrence_condition.rb +272 -0
- data/lib/mhc/property_value/text.rb +11 -0
- data/lib/mhc/property_value/time.rb +45 -0
- data/lib/mhc/query.rb +210 -0
- data/lib/mhc/sync.rb +46 -0
- data/lib/mhc/sync/driver.rb +108 -0
- data/lib/mhc/sync/status.rb +70 -0
- data/lib/mhc/sync/status_manager.rb +142 -0
- data/lib/mhc/sync/strategy.rb +233 -0
- data/lib/mhc/sync/syncinfo.rb +98 -0
- data/lib/mhc/templates/config.yml.erb +142 -0
- data/lib/mhc/version.rb +4 -0
- data/lib/mhc/webdav.rb +319 -0
- data/mhc.gemspec +24 -0
- data/samples/DOT.mhc-config.yml +116 -0
- data/samples/japanese-holidays.mhcc +153 -0
- data/samples/mhc-completions.zsh +11 -0
- data/spec/mhc_spec.rb +682 -0
- data/spec/spec_helper.rb +9 -0
- data/xpm/close.xpm +18 -0
- data/xpm/delete.xpm +19 -0
- data/xpm/exit.xpm +18 -0
- data/xpm/month.xpm +18 -0
- data/xpm/next.xpm +18 -0
- data/xpm/next2.xpm +18 -0
- data/xpm/next_year.xpm +18 -0
- data/xpm/open.xpm +19 -0
- data/xpm/prev.xpm +18 -0
- data/xpm/prev2.xpm +18 -0
- data/xpm/prev_year.xpm +18 -0
- data/xpm/save.xpm +19 -0
- data/xpm/today.xpm +18 -0
- metadata +214 -0
data/icons/Vacation.xbm
ADDED
@@ -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, };
|
data/icons/Vacation.xpm
ADDED
@@ -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
|
data/lib/mhc/builder.rb
ADDED
@@ -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
|