vpim 0.658 → 0.695

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,9 @@
1
+ 0.695 - 2009-03-01
2
+
3
+ - Alpha versions of vPim agent (http://agent.octetcloud.com)
4
+ - Fixed: reminder app had drifted out of sync with API change
5
+
6
+
1
7
  0.657 - 2008-08-14
2
8
 
3
9
  - Date#to_time renamed to Date#vpim_to_time. Apparently Rails also patches the core.
@@ -18,8 +18,8 @@ Usage: #{$0} [where]
18
18
 
19
19
  Shows events and todos occuring soon.
20
20
 
21
- By default, the Apple iCal v3 calendars are used, but if a location where
22
- .ics files is specified, any calendars found there will be used.
21
+ By default, the Apple iCal v3 calendars are used, but if a location where .ics
22
+ files can be found is specified, any calendars found there will be used.
23
23
 
24
24
  Options
25
25
  -h,--help Print this helpful message.
@@ -29,6 +29,7 @@ Options
29
29
  EOF
30
30
 
31
31
  opt_debug = nil
32
+ opt_dump = nil
32
33
  opt_verbose = nil
33
34
  opt_days = 7
34
35
 
@@ -53,6 +54,9 @@ opts.each do |opt, arg|
53
54
 
54
55
  when "--debug" then
55
56
  opt_verbose = true
57
+ if opt_debug
58
+ opt_dump = true
59
+ end
56
60
  opt_debug = true
57
61
  end
58
62
  end
@@ -64,12 +68,12 @@ if ARGV.length > 0
64
68
  calendars << cal
65
69
  end
66
70
  else
67
- Vpim::Repo::Ical3.each() do |cal|
71
+ Vpim::Repo::Apple3.new.each() do |cal|
68
72
  calendars << cal
69
73
  end
70
74
  end
71
75
 
72
- if opt_debug
76
+ if opt_dump
73
77
  pp ARGV
74
78
  pp calendars
75
79
  end
@@ -81,7 +85,7 @@ t0[0] = t0[1] = t0[2] = 0 # sec,min,hour = 0
81
85
  t0 = Time.local(*t0)
82
86
  t1 = t0 + opt_days * SECSPERDAY
83
87
 
84
- if opt_debug
88
+ if opt_dump
85
89
  puts "to: #{t0}"
86
90
  puts "t1: #{t1}"
87
91
  end
@@ -95,12 +99,13 @@ all_events = []
95
99
  all_todos = []
96
100
 
97
101
  calendars.each do |cal|
98
- if opt_debug; puts cal.name; end
102
+ if opt_debug; puts "Calendar: #{cal.name}"; end
99
103
 
104
+ # TODO - mv collection algorithm to library
100
105
  begin
101
106
  cal.events.each do |e|
102
107
  begin
103
- if opt_debug; pp e; end
108
+ if opt_dump; pp e; end
104
109
  if e.occurs_in?(t0, t1)
105
110
  if e.summary
106
111
  all_events.push(e)
@@ -111,12 +116,13 @@ calendars.each do |cal|
111
116
  end
112
117
  end
113
118
 
114
- all_todos.concat(cal.todos)
119
+ all_todos.concat(cal.todos.to_a)
115
120
  end
116
121
  end
117
122
 
118
123
  puts
119
124
 
125
+ # TODO - mv sorting algorithm to library
120
126
  def start_of_first_occurrence(t0, t1, e)
121
127
  e.occurrences(t1) do |t|
122
128
  # An event might start before t0, but end after it..., in which case
@@ -137,7 +143,7 @@ all_events.each do |e|
137
143
 
138
144
  if opt_verbose
139
145
  if e.description; puts " description=#{e.description}"; end
140
- if e.comments; puts " comment=#{e.comments.first}"; end
146
+ if e.comments.any?; puts " comment=#{e.comments.first}"; end
141
147
  if e.location; puts " location=#{e.location}"; end
142
148
  if e.status; puts " status=#{e.status}"; end
143
149
  if e.dtstart; puts " dtstart=#{e.dtstart}"; end
@@ -145,15 +151,15 @@ all_events.each do |e|
145
151
  end
146
152
 
147
153
  i = 1
148
- e.occurrences.each_until(t1).each do |t|
154
+ e.occurrences(t1) do |t|
149
155
  # An event might start before t0, but end after it..., in which case
150
156
  # we are still interested.
151
157
  dstr = ''
152
158
  if e.duration
153
- d = e.duration
154
159
  dstr = " for #{Vpim::Duration.new(e.duration).to_s}"
155
160
  end
156
161
 
162
+ # TODO - mv to library, as variant of occurs_in?
157
163
  if (t + (e.duration || 0)) >= t0
158
164
  puts " ##{i} on #{t}#{dstr}"
159
165
  i += 1
data/bin/rrule CHANGED
File without changes
@@ -0,0 +1,194 @@
1
+ =begin
2
+ Copyright (C) 2009 Sam Roberts
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
+
9
+ require 'sinatra'
10
+ require 'vpim/agent/atomize'
11
+ require 'vpim/repo'
12
+ require 'vpim/view'
13
+
14
+ require 'cgi'
15
+
16
+ configure do
17
+ server = Sinatra::Application.server
18
+ set :server, Proc.new {
19
+ if ENV.include?("PHP_FCGI_CHILDREN")
20
+ break "fastcgi" # Must NOT be the correct class name!
21
+ elsif ENV.include?("REQUEST_METHOD")
22
+ break "cgi" # Must NOT be the correct class name!
23
+ else
24
+ # Fall back on whatever it was going to be.
25
+ server
26
+ end
27
+ }
28
+ end
29
+
30
+ # I could wrap the Repo/Calendar/Atomize in a small class that would memoize
31
+ # ical data and atom output. Maybe even do an HTTP head for fast detection of
32
+ # change? Does a calendar have updated information? Can we memoize atom when
33
+ # ics doesn't change?
34
+
35
+ module Vpim
36
+ module Agent
37
+ module App
38
+ def self.atomize(caluri, feeduri)
39
+ repo = Vpim::Repo::Uri.new(caluri)
40
+ cal = repo.find{true}
41
+ cal = View.week(cal)
42
+ feed = Agent::Atomize.calendar(cal, feeduri, caluri, cal.name)
43
+ return feed.to_xml, Agent::Atomize::MIME
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ get '/ics' do
50
+ from = env['QUERY_STRING']
51
+
52
+ url = URI.parse(request.url)
53
+ url.query = nil
54
+ url_base = url.to_s
55
+ url_atom = nil
56
+
57
+ @url_ics = from # ics from here
58
+ @url_atom = nil
59
+
60
+ if not from.empty?
61
+ # Error out if we can't atomize the feed
62
+ Vpim::Agent::App.atomize(from, "http://example.com")
63
+
64
+ url = URI.parse(request.url)
65
+ url.path << "/atom"
66
+ url_atom = url.to_s
67
+ end
68
+
69
+ @url_base = url_base # clean input form
70
+ @url_atom = url_atom # atomized ics from here
71
+
72
+ haml :"ics.haml"
73
+ end
74
+
75
+ post '/ics' do
76
+ from = params[:url]
77
+ url = URI.parse(request.url)
78
+ url.query = from
79
+ redirect url.to_s
80
+ end
81
+
82
+ # When we support other forms..
83
+ #get '/ics/:form' do
84
+ # form = params[:form]
85
+ get '/ics/atom' do
86
+ from = env['QUERY_STRING']
87
+ port = env["SERVER_PORT"].to_i
88
+ here = request.url
89
+
90
+ if from.empty?
91
+ url = URI.parse(here)
92
+ url.path.sub(/atom$/, "")
93
+ redirect here.to_s
94
+ end
95
+
96
+ xml, xmltype = Vpim::Agent::App.atomize(from, here)
97
+
98
+ content_type xmltype
99
+ body xml
100
+ end
101
+
102
+ get '/ics/style.css' do
103
+ content_type 'text/css'
104
+ sass :"ics.sass"
105
+ end
106
+
107
+ error do
108
+ @url_error = CGI.escapeHTML(env['sinatra.error'].inspect)
109
+ haml :"ics.haml"
110
+ end
111
+
112
+ use_in_file_templates!
113
+
114
+ # FIXME - hard-coded :action paths below, bad!
115
+
116
+ __END__
117
+ @@ics.sass
118
+ body
119
+ :background-color gray
120
+ :font-size medium
121
+ a
122
+ :color black
123
+ :font-style italic
124
+ a:hover
125
+ :color darkred
126
+
127
+ #header
128
+ :border-bottom 3px solid darkred
129
+ #title
130
+ :color black
131
+ :font-size large
132
+
133
+ .text
134
+ :width 80%
135
+ -#:color yellow
136
+
137
+ #submit
138
+ :margin-top 30px
139
+ :margin-left 5%
140
+ #form
141
+ :padding
142
+ :top 10px
143
+ :bottom 10px
144
+ :left 10px
145
+ :right 10px
146
+ :text-align left
147
+ #url
148
+ :width 80%
149
+ #button
150
+ :font-weight bold
151
+ :text-align center
152
+
153
+ #subscribe
154
+ :margin-left 5%
155
+ -#.feed
156
+ -# :margin-left 10%
157
+
158
+ #footer
159
+ :border-top 3px solid darkred
160
+ :margin-top 20px
161
+ @@ics.haml
162
+ %html
163
+ %head
164
+ %title Subscribe to calendar feeds as atom feeds
165
+ %link{:href => '/ics/style.css', :media => 'screen', :type => 'text/css'}
166
+ %body
167
+ #header
168
+ %span#title Subscribe to calendar feeds as atom feeds
169
+ #submit
170
+ .text Calendar feeds are great, but sometimes all you want is an atom feed of what's coming up in the next week.
171
+ .text Paste the URL of the calendar below, submit it, and subscribe.
172
+ %form#form{:method => 'POST', :action => '/ics'}
173
+ %input#url{:name => 'url', :value => params[:url]}
174
+ %input#button{:type => 'submit', :value => 'Submit'}
175
+ - if @url_atom
176
+ #subscribe
177
+ .text
178
+ Subscribe to
179
+ %a{:href => @url_ics}= @url_ics
180
+ as:
181
+ %ul.feed
182
+ %a{:href => @url_atom}= @url_atom
183
+ (atom feed)
184
+ - if @url_error
185
+ #error.text
186
+ #preamble Sorry, trying to access:
187
+ #source= @url_ics
188
+ #transition resulted in:
189
+ #destination= @url_error
190
+ #footer
191
+ .text
192
+ :textile
193
+ Coming from the "Octet Cloud":http://octetcloud.com/ using "vPim":http://vpim.rubyforge.org/, piloted by cloud monkey "Sam Roberts":mailto:vieuxtech@gmail.com
194
+
@@ -0,0 +1,101 @@
1
+ =begin
2
+ Copyright (C) 2009 Sam Roberts
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
+
9
+ require "atom"
10
+
11
+ module Vpim
12
+ module Agent
13
+
14
+ module Atomize
15
+ MIME = "application/atom+xml"
16
+
17
+ # +ical+, an icalendar, or at least a Repo calendar's subset of an Icalendar
18
+ # +feeduri+, the atom xml should know the URI of where the feed is available from.
19
+ # +caluri+, optionally, the URI of the calendar its converted from.
20
+ #
21
+ # TODO - and the URI of an alternative/html representation of this feed?
22
+ def self.calendar(ical, feeduri, caluri = nil, calname = nil)
23
+ mime = MIME
24
+
25
+ feeduri = feeduri.to_str
26
+ caluri = caluri
27
+ calname = (calname or caluri or "Unknown").to_str
28
+
29
+ f = Atom::Feed.new
30
+ # Mandatory attributes:
31
+ # For ID, we should use http://.../ics/atom?....., or just the URL of the ics?
32
+ # I think it can be a full URI... or maybe a sha-1 of our full URI?
33
+ # or like gmail, no id for feed,
34
+ # <id>tag:gmail.google.com,2004:1295062805013769502</id>
35
+ #
36
+ f.id = feeduri
37
+ f.title = calname
38
+ f.updated = Time.now
39
+ f.authors << Atom::Person.new(:name => (caluri or calname))
40
+ f.generator = Atom::Generator.new do |g|
41
+ g.name = Vpim::PRODID
42
+ g.uri = "http://vpim.rubyforge.org"
43
+ g.version = Vpim::VERSION
44
+ end
45
+
46
+ f.links << Atom::Link.new do |l|
47
+ l.href = feeduri
48
+ l.type = mime
49
+ l.rel = :self
50
+ end
51
+
52
+ if caluri
53
+ # This is maybe better described as :via, but with :alternate being
54
+ # an html view of this feed.
55
+ f.links << Atom::Link.new do |l|
56
+ l.href = caluri
57
+ l.type = "text/calendar"
58
+ l.rel = :alternate
59
+ end
60
+ end
61
+
62
+ # .icon = uri to the vAgent icon
63
+ entry_id = 0
64
+ ical.events do |ve|
65
+ # TODO - infinite?
66
+ ve.occurrences do |t|
67
+ f.entries << Atom::Entry.new do |e|
68
+ # iCalendar -> atom
69
+ # -----------------
70
+ # summary -> title
71
+ # description -> text/content
72
+ # uid -> id
73
+ # created -> published?
74
+ # organizer -> author?
75
+ # contact -> author?
76
+ # last-mod -> semantically, this is updated, but atom doesn't
77
+ # have the notion that an entry has a relationship to a time,
78
+ # other than the time the entry itself was published, and when
79
+ # the entry gets updated. We'll abuse updated for the event's time.
80
+ # categories -> where do "tags" go in atom, if anywhere?
81
+ # attachment -> into a link?
82
+ e.title = ve.summary if ve.summary
83
+ e.content = Atom::Content::Text.new(ve.description) if ve.description
84
+ e.updated = t
85
+
86
+ # Use "tag:", as defined by RFC4151, and use event UID if possible. Otherwise,
87
+ # construct something. Maybe I should mix something in to make it unique for
88
+ # each time a feed is generated for the calendar?
89
+ entry_id += 1
90
+ tag = ve.uid || "#{entry_id}@#{feeduri}"
91
+ e.id = "tag:vpim.rubyforge.org,2009:#{tag}"
92
+ end
93
+ end
94
+ end
95
+ return f
96
+ end
97
+ end # Atomize
98
+
99
+ end # Agent
100
+ end # Vpim
101
+
@@ -0,0 +1,173 @@
1
+ =begin
2
+ Copyright (C) 2008 Sam Roberts
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
+
9
+ require "cgi"
10
+ require "uri"
11
+
12
+ require "vpim/repo"
13
+ require "vpim/agent/atomize"
14
+
15
+ module Vpim
16
+ module Agent
17
+ # On failure, raise this with an error message. text/plain for now,
18
+ # text/html later. Will convert to a 404 and a message.
19
+ class NotFound < Exception
20
+ def initialize(name, path)
21
+ super %{Resource "#{name}" under "#{path.prefix}" was not found!}
22
+ end
23
+ end
24
+
25
+ class Path
26
+ def self.split_path(path)
27
+ begin
28
+ path = path.to_ary
29
+ rescue NameError
30
+ path = path.split("/")
31
+ end
32
+ path.map{|w| CGI.unescape(w)}
33
+ end
34
+
35
+ # URI is the uri being queried, base is where this path is mounted under?
36
+ def initialize(uri, base = "")
37
+ @uri = URI.parse(uri.to_s)
38
+ #pp [uri, base, @uri]
39
+ if @uri.path.size == 0
40
+ @uri.path = "/"
41
+ end
42
+ @path = Path.split_path(@uri.path)
43
+ @base = base.to_str
44
+ @mark = 0
45
+
46
+ @base.split.size.times{ shift }
47
+ end
48
+
49
+ def uri
50
+ @uri.to_s
51
+ end
52
+
53
+ def to_path
54
+ self
55
+ end
56
+
57
+ # TODO - call this #next
58
+ def shift
59
+ if @path[@mark]
60
+ @path[@mark += 1]
61
+ end
62
+ end
63
+
64
+ def append(name, scheme = nil)
65
+ uri = @uri.dup
66
+ uri.path += "/" + CGI.escape(name)
67
+ if scheme
68
+ uri.scheme = scheme
69
+ end
70
+ uri
71
+ end
72
+
73
+ def prefix(len = nil)
74
+ len ||= @mark
75
+ @path[0, len].map{|p| CGI.escape(p)}.join("/") + "/"
76
+ end
77
+
78
+ end
79
+
80
+ module Form
81
+ ATOM = Atomize::MIME
82
+ HTML = "text/html"
83
+ ICS = "text/calendar"
84
+ PLAIN = "text/plain"
85
+ VCF = "text/directory"
86
+ end
87
+
88
+ # Return an HTML description of a list of resources accessible under this
89
+ # path.
90
+ class ResourceList
91
+ def initialize(description, items)
92
+ @description = description
93
+ @items = items
94
+ end
95
+
96
+ def get(path)
97
+ return <<__, Form::HTML
98
+ <html><body>
99
+ #{@description}
100
+ <ul>
101
+ #{
102
+ @items.map do |name,description,scheme|
103
+ "<li><a href=\"#{path.append(name,scheme)}\">#{description || name}</a></li>\n"
104
+ end
105
+ }
106
+ </ul>
107
+ </body></html>
108
+ __
109
+ end
110
+ end
111
+
112
+ # Return calendar information based on RESTful (lovein' the jargon...)
113
+ # paths. Input is a Vpim::Repo.
114
+ #
115
+ # .../coding/month/atom
116
+ # .../coding/events/month/ics <- next month?
117
+ # .../coding/events/month/2008-04/ics <- a specified month?
118
+ # .../coding/week/atom
119
+ # .../year/atom
120
+ class Calendars
121
+ def initialize(repo)
122
+ @repo = repo
123
+ end
124
+
125
+ class Calendar
126
+ def initialize(cal)
127
+ @cal = cal
128
+ @list = ResourceList.new(
129
+ "Calendar #{@cal.name.inspect}:",
130
+ [
131
+ ["calendar", "download"],
132
+ ["calendar", "subscription", "webcal"],
133
+ ["atom", "syndication"],
134
+ ]
135
+ )
136
+ end
137
+
138
+ def get(path)
139
+ form = path.shift
140
+
141
+ # TODO should redirect to an object, so that extra paths can be
142
+ # handled more gracefully.
143
+ case form
144
+ when nil
145
+ return @list.get(path)
146
+ when "calendar"
147
+ return @cal.encode, Form::ICS
148
+ when "atom"
149
+ return Atomize.calendar(@cal, path.uri, nil, @cal.name).to_xml, Form::ATOM
150
+ else
151
+ raise NotFound.new(form, path)
152
+ end
153
+ end
154
+ end
155
+
156
+ # Get object at this path. Return value is a tuple of data and mime content type.
157
+ def get(path)
158
+ case name = path.to_path.shift
159
+ when nil
160
+ list = ResourceList.new("Calendars:", @repo.map{|c| c.name})
161
+ return list.get(path)
162
+ else
163
+ if cal = @repo.find{|c| c.name == name}
164
+ return Calendar.new(cal).get(path)
165
+ else
166
+ raise NotFound.new(name, path)
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+