require 'etc' require 'rss/maker' require 'socket' require 'webrick' require 'vpim/icalendar' require 'vpim/vcard' # Notes on debugging with dnssd API: check system.log, it should give info. #require 'pp' #module Kernel # def to_pp # s = PP.pp(self, '') # s.chomp! # s # end #end #-------------------------------------------------------------------------------- # Load DNSSD support, if possible. # TODO - should be in net/dns/dnssd # FIXME - doesn't work with dnssd @avoid_native = true begin if @avoid_native raise LoadError else require 'dnssd' end rescue LoadError begin require 'net/dns/mdns-sd' DNSSD = Net::DNS::MDNSSD rescue LoadError DNSSD = nil end end #-------------------------------------------------------------------------------- # Set up server environment. # Find the user's name and host. require 'etc' $user = Etc.getpwuid(Process.uid).gecos $host = Socket.gethostname $services = [] $stuff = [] def register(name, path, protocol = 'http') # $services << DNSSD.register(name, '_http._tcp', 'local', $port, 'path' => path ) puts "register #{name.inspect} on path #{path.inspect} with #{protocol}" $stuff << [name, path, protocol] end # Create a HTTP server, possibly on a dynamic port. # - Dynamic ports don't actually work well. While it is true we can advertise them, # non-DNSSD aware clients will store the hostname and port when they subscribe to # a calendar, but when the server restarts the port will be different. Not good. $port = 8191 server = WEBrick::HTTPServer.new( :Port => $port ) if $port == 0 # Server may have created multiple listeners, all on a different dynamically # assigned port. families = Socket.getaddrinfo(nil, 1, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE) listeners = [] families.each do |af, one, dns, addr| listeners << TCPServer.new(addr, $port) $port = listeners.first.addr[1] unless $port != 0 end listeners.each do |s| puts "listen on #{s.addr.inspect}" end # So we replace them with our TCPServer sockets which are all on the same # (dynamically assigned) port. server.listeners.each do |s| s.close end server.listeners.replace listeners server.config[:Port] = $port end server.config[:MimeTypes]['ics'] = 'text/calendar' server.config[:MimeTypes]['vcf'] = 'text/directory' #-------------------------------------------------------------------------------- # Mount services ##### Vcard Birthdays as iCalendar $vcf_bday_file = 'vpim-bday.vcf' $vcf_bday_path = '/vcf/bday.ics' class VcfBdayIcsServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, resp) cal = Vpim::Icalendar.create open($vcf_bday_file) do |vcf| Vpim::Vcard.decode(vcf).each do |card| begin bday = card.birthday if bday cal.push Vpim::Icalendar::Vevent.create_yearly( card.birthday, "Birthday for #{card['fn'].strip}" ) $stderr.puts "#{card['fn']} -> bday #{cal.events.last.dtstart}" end rescue $stderr.puts $! $stderr.puts $!.backtrace.join("\n") end end end resp.body = cal.encode resp['content-type'] = 'text/calendar' # Is this necessary, or is it default? raise WEBrick::HTTPStatus::OK end end server.mount( $vcf_bday_path, VcfBdayIcsServlet ) register( "Calendar for all the Birthdays in my vCards", $vcf_bday_path, 'webcal' ) ##### iCalendar as calendars # Export local calendars two different ways $ical_folder = File.expand_path( "~/Library/Calendars" ) # # Here we write a servlet to display all the allowed calendars with # webcal links, so they open in iCal. # $ical_include = /^[A-Z]/ $ical_title = "My Calendars" class IcalIcsServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, resp) body = '' # body << @options.inspect folder, include, exclude = *@options path = req.path_info # body << "\n" # body << "path=#{path.inspect}\n" all = Dir.entries(folder).select do |f| f =~ /\.ics$/ end if include all.reject! do |f| !(f =~ include) end end if exclude all.reject! do |f| f =~ exclude end end # body << "#{all.inspect}\n" if(path == '') body << "