agcaldav 0.2.5.2 → 0.2.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ caldaver-test.sh
2
+ *.gem
3
+ *.tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,8 @@
1
+ = v0.2.5.3
2
+
3
+ * Fixed gemspec
4
+
5
+ = v0.2.5.2
6
+
7
+ * client.update_event *args* changed. UID has to be provided
8
+ now -> client.update_event(:uid => "123", ...)
@@ -0,0 +1,131 @@
1
+ #Ruby CalDAV library named "agcaldav"
2
+ **agcaldav is a CalDAV library based on martinpovolny/ruby-caldav and 4fthawaiian/ruby-caldav and collectiveidea/caldav**
3
+
4
+ **Please keep in mind, agcaldav ist still under heavy development and still not finished...**
5
+
6
+ ##Usage Events
7
+
8
+ First, you've to install the gem
9
+
10
+ gem install agcaldav
11
+
12
+ and require it
13
+
14
+ require "agcaldav"
15
+
16
+ Next you have to obtain the URI, username and password to a CalDAV-Server. If you don't have one try RADICALE (https://github.com/agilastic/Radicale). It's small, simple and written in python. In the following steps I'm using the default params of Radical.
17
+
18
+
19
+ Now you can e.g. create a new AgCalDAV-Client:
20
+
21
+ cal = AgCalDAV::Client.new(:uri => "http://localhost:5232/user/calendar", :user => "user" , :password => "")
22
+
23
+ Alternatively, the proxy parameters can be specified:
24
+
25
+ cal = AgCalDAV::Client.new(:uri => "http://localhost:5232/user/calendar",:user => "user" , :password => "password", :proxy_uri => "http://my-proxy.com:8080")
26
+
27
+
28
+ ####Create an Event
29
+
30
+ result = cal.create_event(:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "12345 12345")
31
+
32
+ Analyze result:
33
+
34
+ >> result
35
+ => #<Icalendar::Event:0x007ff653b47520 @name="VEVENT", @components={}, @properties={"sequence"=>0, "dtstamp"=>#<DateTime: 2012-12-30T19:59:04+00:00 (26527957193/10800,0/1,2299161)>, "description"=>"sdkvjsdf sdkf sdkfj sdkf dsfj", "dtend"=>#<DateTime: 2012-12-30T12:00:00+00:00 (2456292/1,0/1,2299161)>, "dtstart"=>#<DateTime: 2012-12-29T10:00:00+00:00 (29475491/12,0/1,2299161)>, "summary"=>"12345", "uid"=>"e795c480-34e0-0130-7d1d-109add70606c", "x-radicale_name"=>"e795c480-34e0-0130-7d1d-109add70606c.ics"}>
36
+
37
+ >> result.class
38
+ => Icalendar::Event
39
+
40
+
41
+ get UID of this Event:
42
+
43
+ >> result.uid
44
+ => "e795c480-34e0-0130-7d1d-109add70606c"
45
+
46
+
47
+ ####Find an Event (via UUID)
48
+
49
+ result = cal.find_event("e795c480-34e0-0130-7d1d-109add70606c")
50
+
51
+ >> result.class
52
+ => Icalendar::Event
53
+
54
+
55
+ ####Find Events within time interval
56
+
57
+ result = cal.find_events(:start => "2012-10-01 08:00", :end => "2013-01-01")
58
+
59
+ >> result
60
+ => [#<Icalendar::Event:0x007f8ad11cfdf0 @name="VEVENT", @components={}, @properties={"sequence"=>0, "dtstamp"=>#<DateTime: 2012-12-31T13:44:10+00:00 (4244474429/1728,0/1,2299161)>, "description"=>"sdkvjsdf sdkf sdkfj sdkf dsfj", "dtend"=>#<DateTime: 2012-12-30T12:00:00+00:00 (2456292/1,0/1,2299161)>, "dtstart"=>#<DateTime: 2012-12-29T10:00:00+00:00 (29475491/12,0/1,2299161)>, "summary"=>"12345", "uid"=>"b2c45e20-3575-0130-7d2e-109add70606c", "x-radicale_name"=>"b2c45e20-3575-0130-7d2e-109add70606c.ics"}>, #<Icalendar::Event:0x007f8ad10d7dd0 @name="VEVENT", @components={}, @properties={"sequence"=>0, "dtstamp"=>#<DateTime: 2012-12-31T13:44:10+00:00 (4244474429/1728,0/1,2299161)>, "uid"=>"b2c45e20-3575-0130-7d2e-109add70606c", "x-radicale_name"=>"b2c45e20-3575-0130-7d2e-109add70606c.ics"}>]
61
+
62
+ >> result.class
63
+ => Array
64
+
65
+ >> result.count
66
+ => 2
67
+
68
+
69
+
70
+ ####Update Event
71
+
72
+ event = {:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "sdkvjsdf sdkf sdkfj sdkf dsfj"}
73
+ # set UUID
74
+ event[:uid] => "e795c480-34e0-0130-7d1d-109add70606c"
75
+ c = cal.update_event(event)
76
+
77
+
78
+
79
+ ####Delete Event
80
+
81
+ cal.delete_event("e795c480-34e0-0130-7d1d-109add70606c")
82
+
83
+
84
+
85
+
86
+ ##Usage ToDo
87
+
88
+ ####not finished ATM
89
+ Have a look tomorrow...
90
+
91
+
92
+
93
+ ##Work to be done ...
94
+
95
+ 1. find and notify if overlapping events
96
+ 2. code cleanup -> more ActiveRecord style
97
+
98
+
99
+
100
+
101
+ ##Testing
102
+
103
+ agcaldav will use RSpec for its test coverage. Inside the gem
104
+ directory, you can run the specs for RoR 3.x with:
105
+
106
+ rake spec
107
+ (will be implemented in > v0.2.5)
108
+
109
+
110
+
111
+ ##Licence
112
+
113
+ MIT
114
+
115
+
116
+
117
+ ##Contributors
118
+
119
+ [Check all contributors][c]
120
+
121
+
122
+ 1. Fork it.
123
+ 2. Create a branch (`git checkout -b my_feature_branch`)
124
+ 3. Commit your changes (`git commit -am "bugfixed abc..."`)
125
+ 4. Push to the branch (`git push origin my_feature_branch`)
126
+ 5. Open a [Pull Request][1]
127
+ 6. Enjoy a refreshing Club Mate and wait
128
+
129
+ [c]: https://github.com/agilastic/agcaldav/contributors
130
+ [1]: https://github.com/agilastic/agcaldav/pull/
131
+
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ # If you want to make this the default task
6
+ task :default => :spec
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require File.expand_path('../lib/agcaldav/version', __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "agcaldav"
7
+ s.version = AgCalDAV::VERSION
8
+ s.summary = "Ruby CalDAV client"
9
+ s.description = "yet another great Ruby client for CalDAV calendar and tasks."
10
+
11
+ s.required_ruby_version = '>= 1.9.2'
12
+
13
+ s.license = 'MIT'
14
+
15
+ s.homepage = %q{https://github.com/agilastic/agcaldav}
16
+ s.authors = [%q{Alex Ebeling-Hoppe}]
17
+ s.email = [%q{ebeling-hoppe@agilastic.de}]
18
+ s.add_runtime_dependency 'icalendar'
19
+ s.add_runtime_dependency 'uuid'
20
+ s.add_runtime_dependency 'builder'
21
+ s.add_development_dependency "rspec"
22
+ s.add_development_dependency "fakeweb"
23
+
24
+
25
+ s.description = <<-DESC
26
+ agcaldav is yet another great Ruby client for CalDAV calendar. It is based on the icalendar gem.
27
+ DESC
28
+ s.post_install_message = <<-POSTINSTALL
29
+ Changelog: https://github.com/agilastic/agcaldav/blob/master/CHANGELOG.rdoc
30
+ Examples: https://github.com/agilastic/agcaldav
31
+ POSTINSTALL
32
+
33
+
34
+ s.files = `git ls-files`.split("\n")
35
+ s.require_paths = ["lib"]
36
+ end
@@ -0,0 +1,11 @@
1
+ require 'net/https'
2
+ require 'uuid'
3
+ require 'rexml/document'
4
+ require 'rexml/xpath'
5
+ require 'icalendar'
6
+ require 'time'
7
+ require 'date'
8
+
9
+ ['client.rb', 'request.rb', 'net.rb', 'query.rb', 'filter.rb', 'event.rb', 'todo.rb', 'format.rb'].each do |f|
10
+ require File.join( File.dirname(__FILE__), 'agcaldav', f )
11
+ end
@@ -0,0 +1,253 @@
1
+ module AgCalDAV
2
+ class Client
3
+ include Icalendar
4
+ attr_accessor :host, :port, :url, :user, :password, :ssl
5
+
6
+ def format=( fmt )
7
+ @format = fmt
8
+ end
9
+
10
+ def format
11
+ @format ||= Format::Debug.new
12
+ end
13
+
14
+ def initialize( data )
15
+ unless data[:proxy_uri].nil?
16
+ proxy_uri = URI(data[:proxy_uri])
17
+ @proxy_host = proxy_uri.host
18
+ @proxy_port = proxy_uri.port.to_i
19
+ end
20
+ uri = URI(data[:uri])
21
+ @host = uri.host
22
+ @port = uri.port.to_i
23
+ @url = uri.path
24
+ @user = data[:user]
25
+ @password = data[:password]
26
+ @ssl = uri.scheme == 'https'
27
+ end
28
+
29
+ def __create_http
30
+ if @proxy_uri.nil?
31
+ http = Net::HTTP.new(@host, @port)
32
+ else
33
+ http = Net::HTTP.new(@host, @port, @proxy_host, @proxy_port)
34
+ end
35
+ if @ssl
36
+ http.use_ssl = @ssl
37
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
38
+ end
39
+ http
40
+ end
41
+
42
+ def find_events data
43
+ result = ""
44
+ events = []
45
+ res = nil
46
+ __create_http.start {|http|
47
+ req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
48
+ req.basic_auth @user, @password
49
+ req.body = AgCalDAV::Request::ReportVEVENT.new(DateTime.parse(data[:start]).strftime("%Y%m%dT%H%M"),
50
+ DateTime.parse(data[:end]).strftime("%Y%m%dT%H%M") ).to_xml
51
+ res = http.request(req)
52
+ }
53
+ errorhandling res
54
+ result = ""
55
+ xml = REXML::Document.new(res.body)
56
+ REXML::XPath.each( xml, '//c:calendar-data/', {"c"=>"urn:ietf:params:xml:ns:caldav"} ){|c| result << c.text}
57
+ r = Icalendar.parse(result)
58
+ unless r.empty?
59
+ r.each do |calendar|
60
+ calendar.events.each do |event|
61
+ events << event
62
+ end
63
+ end
64
+ events
65
+ else
66
+ return false
67
+ end
68
+ end
69
+
70
+ def find_event uuid
71
+ res = nil
72
+ __create_http.start {|http|
73
+ req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
74
+ req.basic_auth @user, @password
75
+ res = http.request( req )
76
+ }
77
+ errorhandling res
78
+ r = Icalendar.parse(res.body)
79
+ unless r.empty?
80
+ r.first.events.first
81
+ else
82
+ return false
83
+ end
84
+
85
+
86
+ end
87
+
88
+ def delete_event uuid
89
+ res = nil
90
+ __create_http.start {|http|
91
+ req = Net::HTTP::Delete.new("#{@url}/#{uuid}.ics")
92
+ req.basic_auth @user, @password
93
+ res = http.request( req )
94
+ }
95
+ errorhandling res
96
+ if res.code.to_i == 200
97
+ return true
98
+ else
99
+ return false
100
+ end
101
+ end
102
+
103
+ def create_event event
104
+ c = Calendar.new
105
+ c.events = []
106
+ uuid = UUID.new.generate
107
+ raise DuplicateError if entry_with_uuid_exists?(uuid)
108
+ c.event do
109
+ uid uuid
110
+ dtstart DateTime.parse(event[:start])
111
+ dtend DateTime.parse(event[:end])
112
+ categories event[:categories]# Array
113
+ contacts event[:contacts] # Array
114
+ attendees event[:attendees]# Array
115
+ duration event[:duration]
116
+ summary event[:title]
117
+ description event[:description]
118
+ klass event[:accessibility] #PUBLIC, PRIVATE, CONFIDENTIAL
119
+ location event[:location]
120
+ geo_location event[:geo_location]
121
+ status event[:status]
122
+ end
123
+ cstring = c.to_ical
124
+ res = nil
125
+ http = Net::HTTP.new(@host, @port)
126
+ __create_http.start { |http|
127
+ req = Net::HTTP::Put.new("#{@url}/#{uuid}.ics")
128
+ req['Content-Type'] = 'text/calendar'
129
+ req.basic_auth @user, @password
130
+ req.body = cstring
131
+ res = http.request( req )
132
+ }
133
+ errorhandling res
134
+ find_event uuid
135
+ end
136
+
137
+ def update_event event
138
+ #TODO... fix me
139
+ if delete_event event[:uid]
140
+ create_event event
141
+ else
142
+ return false
143
+ end
144
+ end
145
+
146
+ def add_alarm tevent, altCal="Calendar"
147
+
148
+ end
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+ def find_todo uuid
159
+ res = nil
160
+ __create_http.start {|http|
161
+ req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
162
+ req.basic_auth @user, @password
163
+ res = http.request( req )
164
+ }
165
+ errorhandling res
166
+ r = Icalendar.parse(res.body)
167
+ r.first.todos.first
168
+ end
169
+
170
+
171
+
172
+
173
+
174
+ def create_todo todo
175
+ c = Calendar.new
176
+ uuid = UUID.new.generate
177
+ raise DuplicateError if entry_with_uuid_exists?(uuid)
178
+ c.todo do
179
+ uid uuid
180
+ start DateTime.parse(todo[:start])
181
+ duration todo[:duration]
182
+ summary todo[:title]
183
+ description todo[:description]
184
+ klass todo[:accessibility] #PUBLIC, PRIVATE, CONFIDENTIAL
185
+ location todo[:location]
186
+ percent todo[:percent]
187
+ priority todo[:priority]
188
+ url todo[:url]
189
+ geo todo[:geo_location]
190
+ status todo[:status]
191
+ end
192
+ c.todo.uid = uuid
193
+ cstring = c.to_ical
194
+ res = nil
195
+ http = Net::HTTP.new(@host, @port)
196
+ __create_http.start { |http|
197
+ req = Net::HTTP::Put.new("#{@url}/#{uuid}.ics")
198
+ req['Content-Type'] = 'text/calendar'
199
+ req.basic_auth @user, @password
200
+ req.body = cstring
201
+ res = http.request( req )
202
+ }
203
+ errorhandling res
204
+ find_todo uuid
205
+ end
206
+
207
+ def create_todo
208
+ res = nil
209
+ raise DuplicateError if entry_with_uuid_exists?(uuid)
210
+
211
+ __create_http.start {|http|
212
+ req = Net::HTTP::Report.new(@url, initheader = {'Content-Type'=>'application/xml'} )
213
+ req.basic_auth @user, @password
214
+ req.body = AgCalDAV::Request::ReportVTODO.new.to_xml
215
+ res = http.request( req )
216
+ }
217
+ errorhandling res
218
+ format.parse_todo( res.body )
219
+ end
220
+
221
+ private
222
+ def entry_with_uuid_exists? uuid
223
+ res = nil
224
+ __create_http.start {|http|
225
+ req = Net::HTTP::Get.new("#{@url}/#{uuid}.ics")
226
+ req.basic_auth @user, @password
227
+ res = http.request( req )
228
+ }
229
+ if res.body.empty?
230
+ return false
231
+ else
232
+ return true
233
+ end
234
+ end
235
+
236
+ def errorhandling response
237
+ raise AuthenticationError if response.code.to_i == 401
238
+ raise NotExistError if response.code.to_i == 410
239
+ raise APIError if response.code.to_i >= 500
240
+ end
241
+ end
242
+
243
+
244
+
245
+
246
+
247
+ class AgCalDAVError < StandardError
248
+ end
249
+ class AuthenticationError < AgCalDAVError; end
250
+ class DuplicateError < AgCalDAVError; end
251
+ class APIError < AgCalDAVError; end
252
+ class NotExistError < AgCalDAVError; end
253
+ end
@@ -0,0 +1,121 @@
1
+
2
+
3
+ module Icalendar
4
+ # A Event calendar component is a grouping of component
5
+ # properties, and possibly including Alarm calendar components, that
6
+ # represents a scheduled amount of time on a calendar. For example, it
7
+ # can be an activity; such as a one-hour long, department meeting from
8
+ # 8:00 AM to 9:00 AM, tomorrow. Generally, an event will take up time
9
+ # on an individual calendar.
10
+ class Event < Component
11
+ ical_component :alarms
12
+
13
+ ## Single instance properties
14
+
15
+ # Access classification (PUBLIC, PRIVATE, CONFIDENTIAL...)
16
+ ical_property :ip_class, :klass
17
+
18
+ # Date & time of creation
19
+ ical_property :created
20
+
21
+ # Complete description of the calendar component
22
+ ical_property :description
23
+
24
+ attr_accessor :tzid
25
+
26
+ # Specifies date-time when calendar component begins
27
+ ical_property :dtstart, :start
28
+
29
+ # Latitude & longitude for specified activity
30
+ ical_property :geo, :geo_location
31
+
32
+ # Date & time this item was last modified
33
+ ical_property :last_modified
34
+
35
+ # Specifies the intended venue for this activity
36
+ ical_property :location
37
+
38
+ # Defines organizer of this item
39
+ ical_property :organizer
40
+
41
+ # Defines relative priority for this item (1-9... 1 = best)
42
+ ical_property :priority
43
+
44
+ # Indicate date & time when this item was created
45
+ ical_property :dtstamp, :timestamp
46
+
47
+ # Revision sequence number for this item
48
+ ical_property :sequence, :seq
49
+
50
+ # Defines overall status or confirmation of this item
51
+ ical_property :status
52
+ ical_property :summary
53
+ ical_property :transp, :transparency
54
+
55
+ # Defines a persistent, globally unique id for this item
56
+ ical_property :uid, :unique_id
57
+
58
+ # Defines a URL associated with this item
59
+ ical_property :url
60
+ ical_property :recurrence_id, :recurid
61
+
62
+ ## Single but mutually exclusive properties (Not testing though)
63
+
64
+ # Specifies a date and time that this item ends
65
+ ical_property :dtend, :end
66
+
67
+ # Specifies a positive duration time
68
+ ical_property :duration
69
+
70
+ ## Multi-instance properties
71
+
72
+ # Associates a URI or binary blob with this item
73
+ ical_multi_property :attach, :attachment, :attachments
74
+
75
+ # Defines an attendee for this calendar item
76
+ ical_multiline_property :attendee, :attendee, :attendees
77
+
78
+ # Defines the categories for a calendar component (school, work...)
79
+ ical_multi_property :categories, :category, :categories
80
+
81
+ # Simple comment for the calendar user.
82
+ ical_multi_property :comment, :comment, :comments
83
+
84
+ # Contact information associated with this item.
85
+ ical_multi_property :contact, :contact, :contacts
86
+ ical_multi_property :exdate, :exception_date, :exception_dates
87
+ ical_multi_property :exrule, :exception_rule, :exception_rules
88
+ ical_multi_property :rstatus, :request_status, :request_statuses
89
+
90
+ # Used to represent a relationship between two calendar items
91
+ ical_multi_property :related_to, :related_to, :related_tos
92
+ ical_multi_property :resources, :resource, :resources
93
+
94
+ # Used with the UID & SEQUENCE to identify a specific instance of a
95
+ # recurring calendar item.
96
+ ical_multi_property :rdate, :recurrence_date, :recurrence_dates
97
+ ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
98
+
99
+ def initialize()
100
+ super("VEVENT")
101
+
102
+ # Now doing some basic initialization
103
+ sequence 0
104
+ timestamp DateTime.now
105
+ end
106
+
107
+ def alarm(&block)
108
+ a = Alarm.new
109
+ self.add a
110
+
111
+ a.instance_eval(&block) if block
112
+
113
+ a
114
+ end
115
+
116
+ def occurrences_starting(time)
117
+ recurrence_rules.first.occurrences_of_event_starting(self, time)
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,78 @@
1
+ module AgCalDAV
2
+ module Filter
3
+ class Base
4
+ attr_accessor :parent, :child
5
+
6
+ def to_xml(xml = Builder::XmlMarkup.new(:indent => 2))
7
+ if parent
8
+ parent.to_xml
9
+ else
10
+ build_xml(xml)
11
+ end
12
+ end
13
+
14
+ def build_xml(xml)
15
+ #do nothing
16
+ end
17
+
18
+ def child=(child)
19
+ @child = child
20
+ child.parent = self
21
+ end
22
+ end
23
+
24
+ class Component < Base
25
+ attr_accessor :name
26
+
27
+ def initialize(name, parent = nil)
28
+ self.name = name
29
+ self.parent = parent
30
+ end
31
+
32
+ def time_range(range)
33
+ self.child = TimeRange.new(range, self)
34
+ end
35
+
36
+ def uid(uid)
37
+ self.child = Property.new("UID", uid, self)
38
+ end
39
+
40
+ def build_xml(xml)
41
+ xml.tag! "cal:comp-filter", :name => name do
42
+ child.build_xml(xml) unless child.nil?
43
+ end
44
+ end
45
+ end
46
+
47
+ class TimeRange < Base
48
+ attr_accessor :range
49
+
50
+ def initialize(range, parent = nil)
51
+ self.range = range
52
+ self.parent = parent
53
+ end
54
+
55
+ def build_xml(xml)
56
+ xml.tag! "cal:time-range",
57
+ :start => range.begin.to_ical,
58
+ :end => range.end.to_ical
59
+ end
60
+ end
61
+
62
+ class Property < Base
63
+ attr_accessor :name, :text
64
+
65
+ def initialize(name, text, parent = nil)
66
+ self.name = name
67
+ self.text = text
68
+ self.parent = parent
69
+ end
70
+
71
+ def build_xml(xml)
72
+ xml.tag! "cal:prop-filter", :name => self.name do
73
+ xml.tag! "cal:text-match", self.text, :collation => "i;octet"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,54 @@
1
+ module AgCalDAV
2
+ module Format
3
+ class Raw
4
+ def method_missing(m, *args, &block)
5
+ return *args
6
+ end
7
+ end
8
+
9
+ class Debug < Raw
10
+ end
11
+
12
+ class Pretty < Raw
13
+ def parse_calendar(s)
14
+ result = ""
15
+ xml = REXML::Document.new(s)
16
+
17
+ REXML::XPath.each( xml, '//c:calendar-data/', {"c"=>"urn:ietf:params:xml:ns:caldav"} ){|c| result << c.text}
18
+ r = Icalendar.parse(result)
19
+ r
20
+ end
21
+
22
+ def parse_todo( body )
23
+ result = []
24
+ xml = REXML::Document.new( body )
25
+ REXML::XPath.each( xml, '//c:calendar-data/', { "c"=>"urn:ietf:params:xml:ns:caldav"} ){ |c|
26
+ p c.text
27
+ p parse_tasks( c.text )
28
+ result += parse_tasks( c.text )
29
+ }
30
+ return result
31
+ end
32
+
33
+ def parse_tasks( vcal )
34
+ return_tasks = Array.new
35
+ cals = Icalendar.parse(vcal)
36
+ cals.each { |tcal|
37
+ tcal.todos.each { |ttask| # FIXME
38
+ return_tasks << ttask
39
+ }
40
+ }
41
+ return return_tasks
42
+ end
43
+
44
+ def parse_events( vcal )
45
+ Icalendar.parse(vcal)
46
+ end
47
+
48
+ def parse_single( body )
49
+ # FIXME: parse event/todo/vcard
50
+ parse_events( body )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ module Net
2
+ class HTTP
3
+ class Report < HTTPRequest
4
+ METHOD = 'REPORT'
5
+ REQUEST_HAS_BODY = true
6
+ RESPONSE_HAS_BODY = true
7
+ end
8
+
9
+ class Mkcalendar < HTTPRequest
10
+ METHOD = 'MKCALENDAR'
11
+ REQUEST_HAS_BODY = true
12
+ RESPONSE_HAS_BODY = true
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,51 @@
1
+ module AgCalDAV
2
+ class Query
3
+ attr_accessor :child
4
+
5
+ #TODO: raise error if to_xml is called before child is assigned
6
+ def to_xml(xml = Builder::XmlMarkup.new(:indent => 2))
7
+ xml.instruct!
8
+ xml.tag! "cal:calendar-query", AgCalDAV::NAMESPACES do
9
+ xml.tag! "dav:prop" do
10
+ xml.tag! "dav:getetag"
11
+ xml.tag! "cal:calendar-data"
12
+ end
13
+ xml.tag! "cal:filter" do
14
+ cal = Filter::Component.new("VCALENDAR", self)
15
+ cal.child = self.child
16
+ cal.build_xml(xml)
17
+ end
18
+ end
19
+ end
20
+
21
+ def event(param = nil)
22
+ self.child = Filter::Component.new("VEVENT")
23
+ if param.is_a? Range
24
+ self.child.time_range(param)
25
+ elsif param.is_a? String
26
+ self.child.uid(param)
27
+ else
28
+ self.child
29
+ end
30
+ end
31
+
32
+ def todo(param = nil)
33
+ self.child = Filter::Component.new("VTODO")
34
+ self.child
35
+ end
36
+
37
+ def child=(child)
38
+ child.parent = self
39
+ @child = child
40
+ end
41
+
42
+ def self.event( param=nil )
43
+ self.new.event( param )
44
+ end
45
+
46
+
47
+ def self.todo( param=nil )
48
+ self.new.todo
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ require 'builder'
2
+
3
+ module AgCalDAV
4
+ NAMESPACES = { "xmlns:d" => 'DAV:', "xmlns:c" => "urn:ietf:params:xml:ns:caldav" }
5
+ module Request
6
+ class Base
7
+ def initialize
8
+ @xml = Builder::XmlMarkup.new(:indent => 2)
9
+ @xml.instruct!
10
+ end
11
+ attr :xml
12
+ end
13
+
14
+ class Mkcalendar < Base
15
+ attr_accessor :displayname, :description
16
+
17
+ def initialize(displayname = nil, description = nil)
18
+ @displayname = displayname
19
+ @description = description
20
+ end
21
+
22
+ def to_xml
23
+ xml.c :mkcalendar, NAMESPACES do
24
+ xml.d :set do
25
+ xml.d :prop do
26
+ xml.d :displayname, displayname unless displayname.to_s.empty?
27
+ xml.tag! "c:calendar-description", description, "xml:lang" => "en" unless description.to_s.empty?
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ class ReportVEVENT < Base
35
+ attr_accessor :tstart, :tend
36
+
37
+ def initialize( tstart=nil, tend=nil )
38
+ @tstart = tstart
39
+ @tend = tend
40
+ super()
41
+ end
42
+
43
+ def to_xml
44
+ xml.c 'calendar-query'.intern, NAMESPACES do
45
+ xml.d :prop do
46
+ xml.d :getetag
47
+ xml.c 'calendar-data'.intern
48
+ end
49
+ xml.c :filter do
50
+ xml.c 'comp-filter'.intern, :name=> 'VCALENDAR' do
51
+ xml.c 'comp-filter'.intern, :name=> 'VEVENT' do
52
+ xml.c 'time-range'.intern, :start=> "#{tstart}Z", :end=> "#{tend}Z"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ class ReportVTODO < Base
61
+ def to_xml
62
+ xml.c 'calendar-query'.intern, NAMESPACES do
63
+ xml.d :prop do
64
+ xml.d :getetag
65
+ xml.c 'calendar-data'.intern
66
+ end
67
+ xml.c :filter do
68
+ xml.c 'comp-filter'.intern, :name=> 'VCALENDAR' do
69
+ xml.c 'comp-filter'.intern, :name=> 'VTODO'
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,63 @@
1
+ =begin
2
+ Copyright (C) 2005 Jeff Rose
3
+
4
+ This library is free software; you can redistribute it and/or modify it
5
+ under the same terms as the ruby language itself, see the file COPYING for
6
+ details.
7
+ =end
8
+ module Icalendar
9
+ # A Todo calendar component is a grouping of component
10
+ # properties and possibly Alarm calendar components that represent
11
+ # an action-item or assignment. For example, it can be used to
12
+ # represent an item of work assigned to an individual; such as "turn in
13
+ # travel expense today".
14
+ class Todo < Component
15
+ ical_component :alarms
16
+
17
+ # Single properties
18
+ ical_property :ip_class
19
+ ical_property :completed
20
+ ical_property :created
21
+ ical_property :description
22
+ ical_property :dtstamp, :timestamp
23
+ ical_property :dtstart, :start
24
+ ical_property :geo
25
+ ical_property :last_modified
26
+ ical_property :location
27
+ ical_property :organizer
28
+ ical_property :percent_complete, :percent
29
+ ical_property :priority
30
+ ical_property :recurid, :recurrence_id
31
+ ical_property :sequence, :seq
32
+ ical_property :status
33
+ ical_property :summary
34
+ ical_property :uid, :user_id
35
+ ical_property :url
36
+
37
+ # Single but mutually exclusive TODO: not testing anything yet
38
+ ical_property :due
39
+ ical_property :duration
40
+
41
+ # Multi-properties
42
+ ical_multi_property :attach, :attachment, :attachments
43
+ ical_multiline_property :attendee, :attendee, :attendees
44
+ ical_multi_property :categories, :category, :categories
45
+ ical_multi_property :comment, :comment, :comments
46
+ ical_multi_property :contact, :contact, :contacts
47
+ ical_multi_property :exdate, :exception_date, :exception_dates
48
+ ical_multi_property :exrule, :exception_rule, :exception_rules
49
+ ical_multi_property :rstatus, :request_status, :request_statuses
50
+ ical_multi_property :related_to, :related_to, :related_tos
51
+ ical_multi_property :resources, :resource, :resources
52
+ ical_multi_property :rdate, :recurrence_date, :recurrence_dates
53
+ ical_multi_property :rrule, :recurrence_rule, :recurrence_rules
54
+
55
+ def initialize()
56
+ super("VTODO")
57
+
58
+ sequence 0
59
+ timestamp DateTime.now
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module AgCalDAV
2
+ VERSION="0.2.5.3"
3
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'fakeweb'
4
+
5
+ require 'agcaldav'
6
+
7
+ describe AgCalDAV::Client do
8
+
9
+ before(:each) do
10
+ @c = AgCalDAV::Client.new(:uri => "http://localhost:5232/user/calendar", :user => "user" , :password => "")
11
+ end
12
+
13
+ before(:all) do
14
+ class UUID
15
+ def generate
16
+ "360232b0-371c-0130-9e6b-001999638933"
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+ it "check Class of new calendar" do
23
+ @c.class.to_s.should == "AgCalDAV::Client"
24
+ end
25
+
26
+ it "create one event" do
27
+ uid = UUID.new.generate
28
+ FakeWeb.register_uri(:any, %r{http://user@localhost:5232/user/calendar/(.*).ics}, [{:body => "", :status => ["200", "OK"]},
29
+ {:body => "BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12ss345\nDTEND:20130101T110000\nDTSTAMP:20130101T161708\nDTSTART:20130101T100000\nSEQUENCE:0\nSUMMARY:123ss45\nUID:#{uid}\nX-RADICALE-NAME:#{uid}.ics\nEND:VEVENT\nEND:VCALENDAR", :status => ["200", "OK"]}])
30
+ r = @c.create_event(:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "12345 12345")
31
+ r.should_not be_nil
32
+ end
33
+
34
+ it "delete one events" do
35
+ uid = UUID.new.generate
36
+ FakeWeb.register_uri(:delete, %r{http://user@localhost:5232/user/calendar/(.*).ics}, [{:body => "1 deleted.", :status => ["200", "OK"]}, {:body => "not found", :status => ["404", "Not Found"]}])
37
+ r = @c.delete_event(uid)
38
+ r.should == true
39
+ # second time false
40
+ r = @c.delete_event(uid)
41
+ r.should == false
42
+ end
43
+
44
+
45
+ it "failed create one event DuplicateError" do
46
+ uid = "5385e2d0-3707-0130-9e49-0019996389cc"
47
+ FakeWeb.register_uri(:any, %r{http://user@localhost:5232/user/calendar/(.*).ics}, :body => "BEGIN:VCALENDAR\nPRODID:.....")
48
+ lambda{@c.create_event(:start => "2012-12-29 10:00", :end => "2012-12-30 12:00", :title => "12345", :description => "12345 12345")}.should raise_error(AgCalDAV::DuplicateError)
49
+ end
50
+
51
+
52
+ it "update one event" do
53
+ # same as delete && create
54
+ #TODO
55
+ end
56
+
57
+ it "find one event" do
58
+ uid = "5385e2d0-3707-0130-9e49-001999638982"
59
+ FakeWeb.register_uri(:get, "http://user@localhost:5232/user/calendar/#{uid}.ics", :body => "BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12ss345\nDTEND:20130101T110000\nDTSTAMP:20130101T161708\nDTSTART:20130101T100000\nSEQUENCE:0\nSUMMARY:123ss45\nUID:#{uid}\nX-RADICALE-NAME:#{uid}.ics\nEND:VEVENT\nEND:VCALENDAR")
60
+ r = @c.find_event(uid)
61
+ r.should_not be_nil
62
+ r.uid.should == uid
63
+ end
64
+
65
+
66
+ it "find 2 events" do
67
+ module Net
68
+ # Fakeweb doesn't worke here HTTP-method "REPORT" is unknown
69
+ class HTTP
70
+ def request(req, body = nil, &block)
71
+ self
72
+ end
73
+ def code
74
+ "200"
75
+ end
76
+ def body
77
+ "<?xml version=\"1.0\"?>\n<multistatus xmlns=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n <response>\n <href>/user/calendar/960232b0-371c-0130-9e6b-001999638982.ics</href>\n <propstat>\n <prop>\n <getetag>\"-5984324385549365166\"</getetag>\n <C:calendar-data>BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12345\nDTEND:20010202T120000\nDTSTAMP:20130102T161119\nDTSTART:20010202T080000\nSEQUENCE:0\nSUMMARY:6789\nUID:960232b0-371c-0130-9e6b-001999638982\nX-RADICALE-NAME:960232b0-371c-0130-9e6b-001999638982.ics\nEND:VEVENT\nEND:VCALENDAR\n</C:calendar-data>\n </prop>\n <status>HTTP/1.1 200 OK</status>\n </propstat>\n </response>\n <response>\n <href>/user/calendar/98f067a0-371c-0130-9e6c-001999638982.ics</href>\n <propstat>\n <prop>\n <getetag>\"3611068816283260390\"</getetag>\n <C:calendar-data>BEGIN:VCALENDAR\nPRODID:-//Radicale//NONSGML Radicale Server//EN\nVERSION:2.0\nBEGIN:VEVENT\nDESCRIPTION:12345 12345\nDTEND:20010203T120000\nDTSTAMP:20130102T161124\nDTSTART:20010203T080000\nSEQUENCE:0\nSUMMARY:6789\nUID:98f067a0-371c-0130-9e6c-001999638982\nX-RADICALE-NAME:98f067a0-371c-0130-9e6c-001999638982.ics\nEND:VEVENT\nEND:VCALENDAR\n</C:calendar-data>\n </prop>\n <status>HTTP/1.1 200 OK</status>\n </propstat>\n </response>\n</multistatus>\n\n"
78
+ end
79
+ end
80
+ end
81
+ r = @c.find_events(:start => "2001-02-02 07:00", :end => "2000-02-03 23:59")
82
+ r.should_not be_nil
83
+ r.length.should == 2
84
+ end
85
+
86
+
87
+
88
+ end
@@ -0,0 +1,5 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'rubygems'
3
+ require 'agcaldav'
4
+
5
+ RSpec.configure do |config|
6
+ # some (optional) config here
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: agcaldav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5.2
4
+ version: 0.2.5.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-03 00:00:00.000000000 Z
12
+ date: 2013-01-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: icalendar
@@ -91,16 +91,40 @@ dependencies:
91
91
  - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
- description: yet another great Ruby client for CalDAV calendar and tasks.
95
- email: ebeling-hoppe@agilastic.de
94
+ description: ! ' agcaldav is yet another great Ruby client for CalDAV calendar. It
95
+ is based on the icalendar gem.
96
+
97
+ '
98
+ email:
99
+ - ebeling-hoppe@agilastic.de
96
100
  executables: []
97
101
  extensions: []
98
102
  extra_rdoc_files: []
99
- files: []
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - CHANGELOG.rdoc
107
+ - README.md
108
+ - Rakefile
109
+ - agcaldav.gemspec
110
+ - lib/agcaldav.rb
111
+ - lib/agcaldav/client.rb
112
+ - lib/agcaldav/event.rb
113
+ - lib/agcaldav/filter.rb
114
+ - lib/agcaldav/format.rb
115
+ - lib/agcaldav/net.rb
116
+ - lib/agcaldav/query.rb
117
+ - lib/agcaldav/request.rb
118
+ - lib/agcaldav/todo.rb
119
+ - lib/agcaldav/version.rb
120
+ - spec/agcaldav/client_spec.rb
121
+ - spec/spec.opts
122
+ - spec/spec_helper.rb
100
123
  homepage: https://github.com/agilastic/agcaldav
101
124
  licenses:
102
125
  - MIT
103
- post_install_message:
126
+ post_install_message: ! " Changelog: https://github.com/agilastic/agcaldav/blob/master/CHANGELOG.rdoc\n
127
+ \ Examples: https://github.com/agilastic/agcaldav\n"
104
128
  rdoc_options: []
105
129
  require_paths:
106
130
  - lib
@@ -123,4 +147,3 @@ signing_key:
123
147
  specification_version: 3
124
148
  summary: Ruby CalDAV client
125
149
  test_files: []
126
- has_rdoc: