vpim 0.658 → 0.695

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.
@@ -0,0 +1,327 @@
1
+ require 'etc'
2
+ require 'pp'
3
+ require 'rss/maker'
4
+ require 'socket'
5
+ require 'webrick'
6
+ require 'vpim/icalendar'
7
+ require 'vpim/vcard'
8
+
9
+ #--------------------------------------------------------------------------------
10
+ # Load DNSSD support, if possible.
11
+ # TODO - should be in net/dns/dnssd
12
+ # FIXME - doesn't work with dnssd
13
+ @avoid_native = true
14
+ begin
15
+ if @avoid_native
16
+ raise LoadError
17
+ else
18
+ require 'dnssd'
19
+ end
20
+ rescue LoadError
21
+ begin
22
+ require 'net/dns/mdns-sd'
23
+ DNSSD = Net::DNS::MDNSSD
24
+ rescue LoadError
25
+ DNSSD = nil
26
+ end
27
+ end
28
+
29
+ #--------------------------------------------------------------------------------
30
+ # Set up server environment.
31
+
32
+ # Find the user's name and host.
33
+
34
+ require 'etc'
35
+
36
+ $user = Etc.getpwuid(Process.uid).gecos
37
+ $host = Socket.gethostname
38
+
39
+ $services = []
40
+
41
+ $stuff = []
42
+
43
+ def register(name, path, protocol = 'http')
44
+ # $services << DNSSD.register(name, '_http._tcp', 'local', $port, 'path' => path )
45
+
46
+ puts "register #{name.inspect} on path #{path.inspect} with #{protocol}"
47
+
48
+ $stuff << [name, path, protocol]
49
+ end
50
+
51
+ # Create a HTTP server, possibly on a dynamic port.
52
+ # - Dynamic ports don't actually work well. While it is true we can advertise them,
53
+ # non-DNSSD aware clients will store the hostname and port when they subscribe to
54
+ # a calendar, but when the server restarts the port will be different. Not good.
55
+ $port = 8191
56
+
57
+ server = WEBrick::HTTPServer.new( :Port => $port )
58
+
59
+ if $port == 0
60
+ # Server may have created multiple listeners, all on a different dynamically
61
+ # assigned port.
62
+ families = Socket.getaddrinfo(nil, 1, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)
63
+
64
+ listeners = []
65
+
66
+ families.each do |af, one, dns, addr|
67
+ listeners << TCPServer.new(addr, $port)
68
+ $port = listeners.first.addr[1] unless $port != 0
69
+ end
70
+
71
+ listeners.each do |s|
72
+ puts "listen on #{s.addr.inspect}"
73
+ end
74
+
75
+ # So we replace them with our TCPServer sockets which are all on the same
76
+ # (dynamically assigned) port.
77
+ server.listeners.each do |s| s.close end
78
+ server.listeners.replace listeners
79
+ server.config[:Port] = $port
80
+ end
81
+
82
+ server.config[:MimeTypes]['ics'] = 'text/calendar'
83
+ server.config[:MimeTypes]['vcf'] = 'text/directory'
84
+
85
+ #--------------------------------------------------------------------------------
86
+ # Mount services
87
+
88
+ ##### Vcard Birthdays as iCalendar
89
+ =begin
90
+ $vcf_bday_file = 'vpim-bday.vcf'
91
+ $vcf_bday_path = '/vcf/bday.ics'
92
+
93
+ class VcfBdayIcsServlet < WEBrick::HTTPServlet::AbstractServlet
94
+ def do_GET(req, resp)
95
+ cal = Vpim::Icalendar.create
96
+
97
+ open($vcf_bday_file) do |vcf|
98
+ Vpim::Vcard.decode(vcf).each do |card|
99
+ begin
100
+ bday = card.birthday
101
+ if bday
102
+ cal.push Vpim::Icalendar::Vevent.create_yearly(
103
+ card.birthday,
104
+ "Birthday for #{card['fn'].strip}"
105
+ )
106
+ $stderr.puts "#{card['fn']} -> bday #{cal.events.last.dtstart}"
107
+ end
108
+ rescue
109
+ $stderr.puts $!
110
+ $stderr.puts $!.backtrace.join("\n")
111
+ end
112
+ end
113
+ end
114
+
115
+ resp.body = cal.encode
116
+ resp['content-type'] = 'text/calendar'
117
+
118
+ # Is this necessary, or is it default?
119
+ raise WEBrick::HTTPStatus::OK
120
+ end
121
+ end
122
+
123
+ server.mount( $vcf_bday_path, VcfBdayIcsServlet )
124
+
125
+ register( "Calendar for all the Birthdays in my vCards", $vcf_bday_path, 'webcal' )
126
+ =end
127
+
128
+ ##### iCalendar as calendars
129
+ # Export local calendars two different ways
130
+ =begin
131
+ $ical_folder = File.expand_path( "~/Library/Calendars" )
132
+
133
+ #
134
+ # Here we write a servlet to display all the allowed calendars with
135
+ # webcal links, so they open in iCal.
136
+ #
137
+ $ical_include = /^[A-Z]/
138
+ $ical_title = "My Calendars"
139
+
140
+ class IcalIcsServlet < WEBrick::HTTPServlet::AbstractServlet
141
+ def do_GET(req, resp)
142
+ body = ''
143
+ # body << @options.inspect
144
+
145
+ folder, include, exclude = *@options
146
+
147
+ path = req.path_info
148
+
149
+ # body << "\n"
150
+ # body << "path=#{path.inspect}\n"
151
+
152
+ all = Dir.entries(folder).select do |f| f =~ /\.ics$/ end
153
+
154
+ if include
155
+ all.reject! do |f| !(f =~ include) end
156
+ end
157
+ if exclude
158
+ all.reject! do |f| f =~ exclude end
159
+ end
160
+
161
+ # body << "#{all.inspect}\n"
162
+
163
+ if(path == '')
164
+ body << "<ul>\n"
165
+ all.each do |f|
166
+ n = f.sub('.ics', '')
167
+ body << "<li><a href=\"webcal://#{$host}:#{$port}/calfile/#{f}\">#{n}</a>\n"
168
+ end
169
+ body << "</ul>\n"
170
+ end
171
+
172
+ resp.body = body
173
+ resp['content-type'] = 'text/html'
174
+ raise WEBrick::HTTPStatus::OK
175
+ end
176
+ end
177
+
178
+ server.mount( '/calweb', IcalIcsServlet, $ical_folder, $ical_include)
179
+
180
+ register( "My Calendars as webcal:// Feeds", '/calweb' )
181
+
182
+ #
183
+ # We use the WEBrick file servlet to actually serve calendar files.
184
+ # FIXME - this means that if you guess someone's calendar name, you can
185
+ # download it, despite the rudimentary security.
186
+ #
187
+ server.mount( '/calfile', WEBrick::HTTPServlet::FileHandler, $ical_folder, :FancyIndexing=>true )
188
+
189
+ # For debugging...
190
+ register( 'My Calendar Folder', '/calfile' )
191
+
192
+ =end
193
+
194
+ ##### iCalendar/todo as RSS
195
+ =begin
196
+ $ics_todo_title = 'My Todo Items as an RSS Feed'
197
+ $ics_todo_path = "/ics/todo.rss"
198
+
199
+ class IcalTodoRssServlet < WEBrick::HTTPServlet::AbstractServlet
200
+ def do_GET(req, resp)
201
+ rss = RSS::Maker.make("0.9") do |maker|
202
+ title = $ics_todo_title
203
+ link = 'http:///'
204
+ maker.channel.title = title
205
+ maker.channel.link = link
206
+ maker.channel.description = title
207
+ maker.channel.language = 'en-us'
208
+
209
+ # These are required, or RSS::Maker silently returns nil!
210
+ maker.image.url = "maker.image.url"
211
+ maker.image.title = "maker.image.title"
212
+
213
+ Dir[ $ical_folder + "/*.ics" ].each do |file|
214
+ # TODO: use the open with a block variant
215
+ Vpim::Icalendar.decode(File.open(file)).each do |cal|
216
+ cal.todos.each do |todo|
217
+ if !todo.status || todo.status.upcase != "COMPLETED"
218
+ item = maker.items.new_item
219
+ item.title = todo.summary
220
+ item.link = todo.properties['url'] || link
221
+ item.description = todo.description || todo.summary
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ resp.body = rss.to_s
229
+ resp['content-type'] = 'text/xml'
230
+
231
+ raise WEBrick::HTTPStatus::OK
232
+ end
233
+ end
234
+
235
+ server.mount( $ics_todo_path, IcalTodoRssServlet )
236
+
237
+ register( $ics_todo_title, $ics_todo_path )
238
+ =end
239
+
240
+ ##### Local calendars
241
+
242
+ require "vpim/agent/calendars"
243
+
244
+ $ical_folder = File.expand_path( "~/Library/Calendars" )
245
+
246
+ class CalendarsServlet < WEBrick::HTTPServlet::AbstractServlet
247
+ def do_GET(req, resp)
248
+ body = ''
249
+ # body << @options.inspect
250
+
251
+ folder = *@options
252
+
253
+ # TODO Should be longer lived
254
+ repo = Vpim::Repo::Apple3.new($ical_folder)
255
+ rest = Vpim::Agent::Calendars.new(repo)
256
+ path = Vpim::Agent::Path.new(req.request_uri, req.path)
257
+
258
+ begin
259
+ body, form = rest.get(path)
260
+ status = 200
261
+ rescue Vpim::Agent::NotFound
262
+ body = $!.to_s
263
+ form = "text/plain" # should be HTML!
264
+ status = 404
265
+ end
266
+
267
+ resp.status = status
268
+ resp.body = body
269
+ resp['content-type'] = form
270
+ end
271
+ end
272
+
273
+ server.mount( '/calendars', CalendarsServlet, $ical_folder )
274
+
275
+ register( "Calendars", '/calendars' )
276
+
277
+ #--------------------------------------------------------------------------------
278
+ ## Top-level page.
279
+
280
+ $vpim_title = "vAgent for #{$user}"
281
+
282
+ class VpimServlet < WEBrick::HTTPServlet::AbstractServlet
283
+ def do_GET(req, resp)
284
+ body = <<"EOF"
285
+ <h1>#{$vpim_title}</h1>
286
+
287
+ See:
288
+ <ul>
289
+ EOF
290
+
291
+ $stuff.each do |name,path,protocol|
292
+ #body << "<li><a href=\"#{protocol}://#{$host}:#{$port}#{path}\">#{name}</a>\n"
293
+ #
294
+ # $host is wrong, it lacks the domain - we should derive this from how
295
+ # we are called, or just leave it out.
296
+ body << "<li><a href=\"#{path}\">#{name}</a>\n"
297
+ end
298
+
299
+ body << "</ul>\n"
300
+
301
+ resp.body = body
302
+ resp['content-type'] = 'text/html'
303
+ raise WEBrick::HTTPStatus::OK
304
+ end
305
+ end
306
+
307
+ server.mount( '/', VpimServlet )
308
+
309
+ #--------------------------------------------------------------------------------
310
+ # Run server
311
+
312
+ if DNSSD
313
+ $services << DNSSD.register($vpim_title, '_http._tcp', 'local', $port, 'path' => '/' )
314
+ end
315
+
316
+ ['INT', 'TERM'].each do |signal|
317
+ trap(signal) do
318
+ server.shutdown
319
+ $services.each do |s|
320
+ s.stop
321
+ end
322
+ end
323
+ end
324
+
325
+ server.start
326
+
327
+
@@ -0,0 +1,292 @@
1
+ require 'etc'
2
+ require 'rss/maker'
3
+ require 'socket'
4
+ require 'webrick'
5
+ require 'vpim/icalendar'
6
+ require 'vpim/vcard'
7
+
8
+ # Notes on debugging with dnssd API: check system.log, it should give info.
9
+
10
+ #require 'pp'
11
+ #module Kernel
12
+ # def to_pp
13
+ # s = PP.pp(self, '')
14
+ # s.chomp!
15
+ # s
16
+ # end
17
+ #end
18
+
19
+
20
+ #--------------------------------------------------------------------------------
21
+ # Load DNSSD support, if possible.
22
+ # TODO - should be in net/dns/dnssd
23
+ # FIXME - doesn't work with dnssd
24
+ @avoid_native = true
25
+ begin
26
+ if @avoid_native
27
+ raise LoadError
28
+ else
29
+ require 'dnssd'
30
+ end
31
+ rescue LoadError
32
+ begin
33
+ require 'net/dns/mdns-sd'
34
+ DNSSD = Net::DNS::MDNSSD
35
+ rescue LoadError
36
+ DNSSD = nil
37
+ end
38
+ end
39
+
40
+ #--------------------------------------------------------------------------------
41
+ # Set up server environment.
42
+
43
+ # Find the user's name and host.
44
+
45
+ require 'etc'
46
+
47
+ $user = Etc.getpwuid(Process.uid).gecos
48
+ $host = Socket.gethostname
49
+
50
+ $services = []
51
+
52
+ $stuff = []
53
+
54
+ def register(name, path, protocol = 'http')
55
+ # $services << DNSSD.register(name, '_http._tcp', 'local', $port, 'path' => path )
56
+
57
+ puts "register #{name.inspect} on path #{path.inspect} with #{protocol}"
58
+
59
+ $stuff << [name, path, protocol]
60
+ end
61
+
62
+ # Create a HTTP server, possibly on a dynamic port.
63
+ # - Dynamic ports don't actually work well. While it is true we can advertise them,
64
+ # non-DNSSD aware clients will store the hostname and port when they subscribe to
65
+ # a calendar, but when the server restarts the port will be different. Not good.
66
+ $port = 8191
67
+
68
+ server = WEBrick::HTTPServer.new( :Port => $port )
69
+
70
+ if $port == 0
71
+ # Server may have created multiple listeners, all on a different dynamically
72
+ # assigned port.
73
+ families = Socket.getaddrinfo(nil, 1, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)
74
+
75
+ listeners = []
76
+
77
+ families.each do |af, one, dns, addr|
78
+ listeners << TCPServer.new(addr, $port)
79
+ $port = listeners.first.addr[1] unless $port != 0
80
+ end
81
+
82
+ listeners.each do |s|
83
+ puts "listen on #{s.addr.inspect}"
84
+ end
85
+
86
+ # So we replace them with our TCPServer sockets which are all on the same
87
+ # (dynamically assigned) port.
88
+ server.listeners.each do |s| s.close end
89
+ server.listeners.replace listeners
90
+ server.config[:Port] = $port
91
+ end
92
+
93
+ server.config[:MimeTypes]['ics'] = 'text/calendar'
94
+ server.config[:MimeTypes]['vcf'] = 'text/directory'
95
+
96
+ #--------------------------------------------------------------------------------
97
+ # Mount services
98
+
99
+ ##### Vcard Birthdays as iCalendar
100
+
101
+ $vcf_bday_file = 'vpim-bday.vcf'
102
+ $vcf_bday_path = '/vcf/bday.ics'
103
+
104
+ class VcfBdayIcsServlet < WEBrick::HTTPServlet::AbstractServlet
105
+ def do_GET(req, resp)
106
+ cal = Vpim::Icalendar.create
107
+
108
+ open($vcf_bday_file) do |vcf|
109
+ Vpim::Vcard.decode(vcf).each do |card|
110
+ begin
111
+ bday = card.birthday
112
+ if bday
113
+ cal.push Vpim::Icalendar::Vevent.create_yearly(
114
+ card.birthday,
115
+ "Birthday for #{card['fn'].strip}"
116
+ )
117
+ $stderr.puts "#{card['fn']} -> bday #{cal.events.last.dtstart}"
118
+ end
119
+ rescue
120
+ $stderr.puts $!
121
+ $stderr.puts $!.backtrace.join("\n")
122
+ end
123
+ end
124
+ end
125
+
126
+ resp.body = cal.encode
127
+ resp['content-type'] = 'text/calendar'
128
+
129
+ # Is this necessary, or is it default?
130
+ raise WEBrick::HTTPStatus::OK
131
+ end
132
+ end
133
+
134
+ server.mount( $vcf_bday_path, VcfBdayIcsServlet )
135
+
136
+ register( "Calendar for all the Birthdays in my vCards", $vcf_bday_path, 'webcal' )
137
+
138
+ ##### iCalendar as calendars
139
+ # Export local calendars two different ways
140
+
141
+ $ical_folder = File.expand_path( "~/Library/Calendars" )
142
+
143
+ #
144
+ # Here we write a servlet to display all the allowed calendars with
145
+ # webcal links, so they open in iCal.
146
+ #
147
+ $ical_include = /^[A-Z]/
148
+ $ical_title = "My Calendars"
149
+
150
+ class IcalIcsServlet < WEBrick::HTTPServlet::AbstractServlet
151
+ def do_GET(req, resp)
152
+ body = ''
153
+ # body << @options.inspect
154
+
155
+ folder, include, exclude = *@options
156
+
157
+ path = req.path_info
158
+
159
+ # body << "\n"
160
+ # body << "path=#{path.inspect}\n"
161
+
162
+ all = Dir.entries(folder).select do |f| f =~ /\.ics$/ end
163
+
164
+ if include
165
+ all.reject! do |f| !(f =~ include) end
166
+ end
167
+ if exclude
168
+ all.reject! do |f| f =~ exclude end
169
+ end
170
+
171
+ # body << "#{all.inspect}\n"
172
+
173
+ if(path == '')
174
+ body << "<ul>\n"
175
+ all.each do |f|
176
+ n = f.sub('.ics', '')
177
+ body << "<li><a href=\"webcal://#{$host}:#{$port}/calfile/#{f}\">#{n}</a>\n"
178
+ end
179
+ body << "</ul>\n"
180
+ end
181
+
182
+ resp.body = body
183
+ resp['content-type'] = 'text/html'
184
+ raise WEBrick::HTTPStatus::OK
185
+ end
186
+ end
187
+
188
+ server.mount( '/calweb', IcalIcsServlet, $ical_folder, $ical_include)
189
+
190
+ register( "My Calendars as webcal:// Feeds", '/calweb' )
191
+
192
+ #
193
+ # We use the WEBrick file servlet to actually serve calendar files.
194
+ # FIXME - this means that if you guess someone's calendar name, you can
195
+ # download it, despite the rudimentary security.
196
+ #
197
+ server.mount( '/calfile', WEBrick::HTTPServlet::FileHandler, $ical_folder, :FancyIndexing=>true )
198
+
199
+ # For debugging...
200
+ register( 'My Calendar Folder', '/calfile' )
201
+
202
+ ##### iCalendar/todo as RSS
203
+
204
+ $ics_todo_title = 'My Todo Items as an RSS Feed'
205
+ $ics_todo_path = "/ics/todo.rss"
206
+
207
+ class IcalTodoRssServlet < WEBrick::HTTPServlet::AbstractServlet
208
+ def do_GET(req, resp)
209
+ rss = RSS::Maker.make("0.9") do |maker|
210
+ title = $ics_todo_title
211
+ link = 'http:///'
212
+ maker.channel.title = title
213
+ maker.channel.link = link
214
+ maker.channel.description = title
215
+ maker.channel.language = 'en-us'
216
+
217
+ # These are required, or RSS::Maker silently returns nil!
218
+ maker.image.url = "maker.image.url"
219
+ maker.image.title = "maker.image.title"
220
+
221
+ Dir[ $ical_folder + "/*.ics" ].each do |file|
222
+ # TODO: use the open with a block variant
223
+ Vpim::Icalendar.decode(File.open(file)).each do |cal|
224
+ cal.todos.each do |todo|
225
+ if !todo.status || todo.status.upcase != "COMPLETED"
226
+ item = maker.items.new_item
227
+ item.title = todo.summary
228
+ item.link = todo.properties['url'] || link
229
+ item.description = todo.description || todo.summary
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ resp.body = rss.to_s
237
+ resp['content-type'] = 'text/xml'
238
+
239
+ raise WEBrick::HTTPStatus::OK
240
+ end
241
+ end
242
+
243
+ server.mount( $ics_todo_path, IcalTodoRssServlet )
244
+
245
+ register( $ics_todo_title, $ics_todo_path )
246
+
247
+
248
+ #--------------------------------------------------------------------------------
249
+ ## Top-level page.
250
+
251
+ $vpim_title = "vPim for #{$user}"
252
+
253
+ class VpimServlet < WEBrick::HTTPServlet::AbstractServlet
254
+ def do_GET(req, resp)
255
+ body = <<"EOF"
256
+ <h1>#{$vpim_title}</h1>
257
+
258
+ You can access:
259
+ <ul>
260
+ EOF
261
+
262
+ $stuff.each do |name,path,protocol|
263
+ body << "<li><a href=\"#{protocol}://#{$host}:#{$port}#{path}\">#{name}</a>\n"
264
+ end
265
+
266
+ body << "</ul>\n"
267
+
268
+ resp.body = body
269
+ resp['content-type'] = 'text/html'
270
+ raise WEBrick::HTTPStatus::OK
271
+ end
272
+ end
273
+
274
+ server.mount( '/', VpimServlet )
275
+
276
+ #--------------------------------------------------------------------------------
277
+ # Run server
278
+
279
+ $services << DNSSD.register($vpim_title, '_http._tcp', 'local', $port, 'path' => '/' )
280
+
281
+ ['INT', 'TERM'].each do |signal|
282
+ trap(signal) do
283
+ server.shutdown
284
+ $services.each do |s|
285
+ s.stop
286
+ end
287
+ end
288
+ end
289
+
290
+ server.start
291
+
292
+