Author: assaf
Date: Tue May 20 17:32:12 2008
New Revision: 658515

URL: http://svn.apache.org/viewvc?rev=658515&view=rev
Log:
Revised iCal buildr and added line wrapping.
Tasks now show as todos in OS X iCal.
Added title and subtitle for activity feeds.

Modified:
    ode/sandbox/singleshot/app/controllers/activities_controller.rb
    ode/sandbox/singleshot/app/controllers/tasks_controller.rb
    ode/sandbox/singleshot/app/models/stakeholder.rb
    ode/sandbox/singleshot/app/views/activities/index.atom.builder
    ode/sandbox/singleshot/app/views/activities/index.ics.ical
    ode/sandbox/singleshot/app/views/tasks/index.ics.ical
    ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb
    ode/sandbox/singleshot/db/migrate/20080506015119_stakeholders.rb
    ode/sandbox/singleshot/db/migrate/20080506015153_activities.rb
    ode/sandbox/singleshot/db/schema.rb
    ode/sandbox/singleshot/lib/extensions/ical_template.rb
    ode/sandbox/singleshot/lib/tasks/populate.rake

Modified: ode/sandbox/singleshot/app/controllers/activities_controller.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/controllers/activities_controller.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/controllers/activities_controller.rb (original)
+++ ode/sandbox/singleshot/app/controllers/activities_controller.rb Tue May 20 
17:32:12 2008
@@ -5,6 +5,7 @@
 
   def index
     @title = 'Activities'
+    @subtitle = 'Track activity in tasks you participate in or observe.'
     @alternate = { Mime::ATOM=>formatted_activities_url(:format=>:atom, 
:access_key=>authenticated.access_key),
                    Mime::ICS=>formatted_activities_url(:format=>:ics, 
:access_key=>authenticated.access_key) }
     @activities = Activity.for_stakeholder(authenticated)
@@ -19,7 +20,8 @@
   end
 
   def show
-    @title = "Activities — [EMAIL PROTECTED]"
+    @title = "Activities - [EMAIL PROTECTED]"
+    @subtitle = "Track all activities in the task [EMAIL PROTECTED]"
     @alternate = { Mime::ATOM=>formatted_activity_url(@task, :atom, 
:access_key=>authenticated.access_key),
                    Mime::ICS=>formatted_activity_url(@task, :ics, 
:access_key=>authenticated.access_key) }
     @activities = @task.activities

Modified: ode/sandbox/singleshot/app/controllers/tasks_controller.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/controllers/tasks_controller.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/controllers/tasks_controller.rb (original)
+++ ode/sandbox/singleshot/app/controllers/tasks_controller.rb Tue May 20 
17:32:12 2008
@@ -8,6 +8,8 @@
   before_filter :forbid_reserved, :except=>[:update, :destroy]
 
   def index
+    @title = 'Tasks'
+    @subtitle = 'Tasks you are performing or can claim for your own.'
     @alternate = { Mime::ATOM=>formatted_tasks_url(:format=>:atom, 
:access_key=>authenticated.access_key), 
                    Mime::ICS=>formatted_tasks_url(:format=>:ics, 
:access_key=>authenticated.access_key) }
     @tasks = 
Task.with_stakeholders.for_stakeholder(authenticated).pending.prioritized
@@ -20,6 +22,7 @@
       format.xml  { render :xml=>@task }
       format.json { render :json=>@task }
       format.ics  do
+        @title = @task.title
         @tasks = [EMAIL PROTECTED]
         render :action=>'index'
       end

Modified: ode/sandbox/singleshot/app/models/stakeholder.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/models/stakeholder.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/models/stakeholder.rb (original)
+++ ode/sandbox/singleshot/app/models/stakeholder.rb Tue May 20 17:32:12 2008
@@ -7,8 +7,7 @@
 #  task_id    :integer         not null
 #  person_id  :integer         not null
 #  role       :string(255)     not null
-#  created_at :datetime
-#  updated_at :datetime
+#  created_at :datetime        not null
 #
 
 # Represents a stakeholder in the task.  Identifies the person and their role.

Modified: ode/sandbox/singleshot/app/views/activities/index.atom.builder
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/views/activities/index.atom.builder?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/views/activities/index.atom.builder (original)
+++ ode/sandbox/singleshot/app/views/activities/index.atom.builder Tue May 20 
17:32:12 2008
@@ -1,5 +1,6 @@
 atom_feed :root_url=>activities_url do |feed|
-  feed.title 'Singleshot: Activities'
+  feed.title @title
+  feed.subtitle @subtitle
   feed.updated @activities.first.created_at
 
   for activity in @activities

Modified: ode/sandbox/singleshot/app/views/activities/index.ics.ical
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/views/activities/index.ics.ical?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/views/activities/index.ics.ical (original)
+++ ode/sandbox/singleshot/app/views/activities/index.ics.ical Tue May 20 
17:32:12 2008
@@ -1,6 +1,9 @@
-calendar.prodid = 'Apache Singleshot'
+calendar.prodid '-//Apache.org//Singleshot//EN'
+calendar.x_wr_calname 'Singleshot - ' + @title
+calendar.x_wr_caldesc @subtitle
 for activity in @activities
-  calendar.event do |event|
+  calendar.event activity do |event|
+    event.dtstart activity.created_at
     event.summary "#{activity.person.fullname} #{activity.action} 
#{activity.task.title}"
     event.description activity.task.description
     event.url task_url(activity.task)

Modified: ode/sandbox/singleshot/app/views/tasks/index.ics.ical
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/views/tasks/index.ics.ical?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/views/tasks/index.ics.ical (original)
+++ ode/sandbox/singleshot/app/views/tasks/index.ics.ical Tue May 20 17:32:12 
2008
@@ -1,4 +1,6 @@
-calendar.prodid 'Apache Singleshot'
+calendar.prodid '-//Apache.org//Singleshot//EN'
+calendar.x_wr_calname 'Singleshot - ' + @title
+calendar.x_wr_caldesc @subtitle
 for task in @tasks
   calendar.todo task do |todo|
     todo.summary task.title

Modified: ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb (original)
+++ ode/sandbox/singleshot/db/migrate/20080506015046_tasks.rb Tue May 20 
17:32:12 2008
@@ -1,23 +1,22 @@
 class Tasks < ActiveRecord::Migration
   def self.up
-    create_table :tasks do |t|
-      t.string    :title,        :null=>false
-      t.string    :description,  :null=>false
-      t.integer   :priority,     :null=>false, :limit=>1
-      t.date      :due_on,       :null=>true
-      t.string    :status,       :null=>false
-      t.string    :frame_url,    :null=>true
-      t.string    :outcome_url,  :null=>true
-      t.string    :outcome_type, :null=>true
-      t.string    :access_key,   :null=>true, :limit=>32
-      t.text      :data,         :null=>false
-      t.integer   :version,      :null=>false, :default=>0
+    create_table 'tasks' do |t|
+      t.string    'title',        :null=>false
+      t.string    'description',  :null=>false
+      t.integer   'priority',     :null=>false, :limit=>1
+      t.date      'due_on',       :null=>true
+      t.string    'status',       :null=>false
+      t.string    'frame_url',    :null=>true
+      t.string    'outcome_url',  :null=>true
+      t.string    'outcome_type', :null=>true
+      t.string    'access_key',   :null=>true, :limit=>32
+      t.text      'data',         :null=>false
+      t.integer   'version',      :null=>false, :default=>0
       t.timestamps
     end
-    add_index :tasks, [:status, :updated_at]
   end
 
   def self.down
-    drop_table :tasks
+    drop_table 'tasks'
   end
 end

Modified: ode/sandbox/singleshot/db/migrate/20080506015119_stakeholders.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/migrate/20080506015119_stakeholders.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/migrate/20080506015119_stakeholders.rb (original)
+++ ode/sandbox/singleshot/db/migrate/20080506015119_stakeholders.rb Tue May 20 
17:32:12 2008
@@ -1,17 +1,14 @@
 class Stakeholders < ActiveRecord::Migration
   def self.up
-    create_table :stakeholders do |t|
-      t.integer :task_id,    :null=>false
-      t.integer :person_id,  :null=>false
-      t.string  :role,       :null=>false
-      t.timestamps
+    create_table 'stakeholders' do |t|
+      t.integer   'task_id',    :null=>false
+      t.integer   'person_id',  :null=>false
+      t.string    'role',       :null=>false
+      t.datetime  'created_at', :null=>false
     end
-    add_index :stakeholders, [:task_id, :person_id, :role], :unique=>true
-    add_index :stakeholders, [:task_id, :role]
-    add_index :stakeholders, [:person_id, :role]
   end
 
   def self.down
-    drop_table :stakeholders
+    drop_table 'stakeholders'
   end
 end

Modified: ode/sandbox/singleshot/db/migrate/20080506015153_activities.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/migrate/20080506015153_activities.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/migrate/20080506015153_activities.rb (original)
+++ ode/sandbox/singleshot/db/migrate/20080506015153_activities.rb Tue May 20 
17:32:12 2008
@@ -1,10 +1,10 @@
 class Activities < ActiveRecord::Migration
   def self.up
-    create_table 'activities' do |t|
-      t.integer 'person_id',   :null=>false
-      t.integer 'task_id',     :null=>false
-      t.string  'action',      :null=>false
-      t.datetime 'created_at', :null=>false
+    create_table  'activities' do |t|
+      t.integer   'person_id',  :null=>false
+      t.integer   'task_id',    :null=>false
+      t.string    'action',     :null=>false
+      t.datetime  'created_at', :null=>false
     end
   end
 

Modified: ode/sandbox/singleshot/db/schema.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/schema.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/schema.rb (original)
+++ ode/sandbox/singleshot/db/schema.rb Tue May 20 17:32:12 2008
@@ -39,14 +39,9 @@
     t.integer  "task_id",    :null => false
     t.integer  "person_id",  :null => false
     t.string   "role",       :null => false
-    t.datetime "created_at"
-    t.datetime "updated_at"
+    t.datetime "created_at", :null => false
   end
 
-  add_index "stakeholders", ["person_id", "role"], :name => 
"index_stakeholders_on_person_id_and_role"
-  add_index "stakeholders", ["task_id", "role"], :name => 
"index_stakeholders_on_task_id_and_role"
-  add_index "stakeholders", ["task_id", "person_id", "role"], :name => 
"index_stakeholders_on_task_id_and_person_id_and_role", :unique => true
-
   create_table "tasks", :force => true do |t|
     t.string   "title",                                     :null => false
     t.string   "description",                               :null => false
@@ -63,6 +58,4 @@
     t.datetime "updated_at"
   end
 
-  add_index "tasks", ["status", "updated_at"], :name => 
"index_tasks_on_status_and_updated_at"
-
 end

Modified: ode/sandbox/singleshot/lib/extensions/ical_template.rb
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/extensions/ical_template.rb?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/lib/extensions/ical_template.rb (original)
+++ ode/sandbox/singleshot/lib/extensions/ical_template.rb Tue May 20 17:32:12 
2008
@@ -1,104 +1,85 @@
 module ActionView
-  class ICalBuilder
+  class ICalBuilder < BlankSlate
 
-    module Properties
-      attr_accessor :properties
+    module Encoding
 
     private
 
-      def property(name, value)
-        value = { :value=>value } unless Hash === value
-        name.to_s.underscore.upcase +
-          value.except(:value).map { |name, value| 
";#{name.to_s.underscore}=#{stringify(value)}" }.join +
-          ":#{stringify(value[:value])}"
+      def method_missing(name, *args)
+        params = args.extract_options!
+        write name, args, params
+      end
+
+      # Write a content line the specified name, value and parameters.
+      # Names are automatically converted to upper case with dash separators.
+      def write(name, value, params = {})
+        write_line name.to_s.underscore.upcase +
+          params.map { |name, value| 
";#{name.to_s.underscore.upcase}=#{stringify(value)}" }.join +
+          ":#{stringify(value)}"
+      end
+
+      # Write a content line, observing the 75 character limit and unfolding 
rules.
+      def write_line(line)
+        if line.size <= 75
+          @output << "#{line}\r\n"
+        else
+          @output << "#{line[0...75]}\r\n"
+          write_line " #{line[75..-1]}"
+        end
       end
 
+      # Convert Ruby value into most appropriate iCal representation.
       def stringify(value)
         case value
-        when String then value
+        when Array then value.map { |item| stringify(item) }.join(',')
         when Date then value.strftime('%Y%m%d')
         when Time then value.strftime(value.utc? ? '%Y%m%dT%H%M%SZ' : 
'%Y%m%dT%H%M%S')
         else value.to_s
         end
       end
 
-      def method_missing(name, *args)
-        options = args.extract_options!
-        options[:value] = args.first
-        @properties[name] = options
-      end
     end
 
-    class Component
 
-      include Properties
-
-      def initialize(request, record = nil)
-        @properties = {}
-        if record
-          uid "#{request.host}:#{record.class}/#{record.id}"
-          dtstamp record.created_at.utc.strftime('%Y%m%dT%H%M%SZ') if 
record.respond_to?(:created_at)
-          last_modified record.updated_at.utc.strftime('%Y%m%dT%H%M%SZ') if 
record.respond_to?(:updated_at)
-          sequence record.send(record.class.locking_column) if 
record.locking_enabled?
-        end
-      end
-
-      def to_ical
-        # TODO: escaping for values
-        # TODO: break up long lines
-        # TODO: all other conformance requirements
-        properties = @properties.map { |name, value| property(name, value) }
-        ["BEGIN:#{self.class.const_get :NAME}", properties, 
"END:#{self.class.const_get :NAME}"].flatten.join("\n")
-      end
+    include Encoding
 
+    def initialize(request, output = nil)
+      @request = request
+      @output = output || StringIO.new
+      write 'BEGIN', 'VCALENDAR'
+      write 'VERSION', '2.0'
+      yield self
+      write 'END', 'VCALENDAR'
     end
 
-    class Event < Component
-
-      NAME = 'VEVENT'
-
+    def event(record, &block)
+      component 'vevent', record, &block
     end
 
-
-    class Todo < Component
-
-      NAME = 'VTODO'
-
-    end
-
-    include Properties
-
-    def initialize(request)
-      @request =request
-      @properties = { :method=>'PUBLISH' }
-      @components = []
+    def todo(record, &block)
+      component 'vtodo', record, &block
     end
 
-    attr_reader :components
-
-    def event(record = nil)
-      returning Event.new(@request, record) do |event|
-        yield event if block_given?
-        @components << event
-      end
+    def journal(record, &block)
+      component 'vjournal', record, &block
     end
 
-    def todo(record = nil)
-      returning Todo.new(@request, record) do |todo|
-        yield todo if block_given?
-        @components << todo
-      end
+    def to_s
+      @output.respond_to?(:string) ? @output.string : @output.to_s
     end
 
-    def to_ical
-      # TODO: user's timezone
-      properties = @properties.map { |name, value| property(name, value) }
-      components = @components.map(&:to_ical)
-      ['BEGIN:VCALENDAR', 'VERSION:2.0', properties, components, 
'END:VCALENDAR'].flatten.join("\n")
-    end
+  private
 
-    def content_type
-      "#{Mime::ICS};method=#{properties[:method]}"
+    def component(type, record)
+      write 'BEGIN', type.upcase
+      if record
+        uid           MD5.hexdigest([EMAIL PROTECTED], record.class, 
record.id, record.created_at].join(':'))
+        dtstamp       record.created_at.utc.strftime('%Y%m%dT%H%M%SZ') if 
record.respond_to?(:created_at)
+        last_modified record.updated_at.utc.strftime('%Y%m%dT%H%M%SZ') if 
record.respond_to?(:updated_at)
+        sequence      record.send(record.class.locking_column) if 
record.locking_enabled?
+      end
+      yield self
+      write 'END', type.upcase
     end
 
   end
@@ -114,10 +95,13 @@
 
       def compile(template)
         content_type_handler = 
(@view.send!(:controller).respond_to?(:response) ? "controller.response" : 
"controller")
-        "calendar = ::ActionView::ICalBuilder.new(request)\n" +
-        template.source +
-        "#{content_type_handler}.content_type ||= calendar.content_type\n" +
-        "\ncalendar.to_ical\n"
+        <<-RUBY
+        #{content_type_handler}.content_type ||= "#{Mime::ICS};method=PUBLISH"
+        ical = ActionView::ICalBuilder.new controller.request do |calendar|
+          #{template.source}
+        end
+        ical.to_s
+        RUBY
       end
 
       def cache_fragment(block, name = {}, options = nil)

Modified: ode/sandbox/singleshot/lib/tasks/populate.rake
URL: 
http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/tasks/populate.rake?rev=658515&r1=658514&r2=658515&view=diff
==============================================================================
--- ode/sandbox/singleshot/lib/tasks/populate.rake (original)
+++ ode/sandbox/singleshot/lib/tasks/populate.rake Tue May 20 17:32:12 2008
@@ -11,39 +11,55 @@
     end
 
     puts "Populating database for #{you.identity}"
-    url = 'http://localhost:3001/sandwich'
     other = Person.identify('anon') || Person.create(:email=>'[EMAIL 
PROTECTED]')
     Activity.delete_all
     Stakeholder.delete_all
     Task.delete_all
-    create = lambda do |attributes|
+
+    def retract(*models)
+      models.each do |model|
+        model.all.each do |record|
+          change = ['created_at = ?', record.created_at - 4.hours]
+          if record.respond_to?(:updated_at)
+            change.first << ', updated_at = ?'
+            change << record.updated_at - 1.hours
+          end
+          model.update_all change, :id=>record.id 
+        end
+      end
+    end
+
+    def create(attributes)
+      retract Task, Stakeholder, Activity
       attributes = { :title=>Faker::Lorem.sentence, 
:description=>Faker::Lorem.paragraph,
-                     :frame_url=>url, :modified_by=>you }.merge(attributes || 
{})
+                     :frame_url=>'http://localhost:3001/sandwich', 
:modified_by=>Person.find_by_identity(ENV['USER']) }.
+                     merge(attributes || {})
       Task.create!(attributes)
     end
 
+
     # Tasks you should not see.
-    create[:title=>'You will not see this task since this task is reserved.', 
:status=>'reserved', :creator=>you]
-    create[:title=>'You will not see this task since you are not a 
stakeholder.']
+    create :title=>'You will not see this task since this task is reserved.', 
:status=>'reserved', :creator=>you
+    create :title=>'You will not see this task since you are not a 
stakeholder.'
     # Tasks in which we are:
     # - creator
     # - owner
     # - observer
     # - admin
-    create[:creator=>you]
-    create[:creator=>you, :owner=>you]
-    create[:observers=>you]
-    create[:admins=>you]
+    create :creator=>you
+    create :creator=>you, :owner=>you
+    create :observers=>you
+    create :admins=>you
     # Tasks in which we are only or one of many potential owners.
-    create[:potential_owners=>you]
-    create[:potential_owners=>[you, other]]
-    create[:owner=>other, :potential_owners=>you]
+    create :potential_owners=>you
+    create :potential_owners=>[you, other]
+    create :owner=>other, :potential_owners=>you
     # High priority should show first.
-    create[:owner=>you, :priority=>Task::PRIORITIES.first]
+    create :owner=>you, :priority=>Task::PRIORITIES.first
     # Over-due before due today before anything else.
-    create[:owner=>you, :due_on=>Time.today - 1.day]
-    create[:owner=>you, :due_on=>Time.today]
-    create[:owner=>you, :due_on=>Time.today + 1.day]
+    create :owner=>you, :due_on=>Time.today - 1.day
+    create :owner=>you, :due_on=>Time.today
+    create :owner=>you, :due_on=>Time.today + 1.day
   end
 
 end


Reply via email to