mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-16 23:30:12 +01:00
* Started to add some of the framework for iCal integration (not working yet)
* Added validation for next actions (on description and notes field) git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@7 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
parent
2b65b3162b
commit
49b8fa1007
6 changed files with 326 additions and 7 deletions
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
require_dependency "login_system"
|
||||
require_dependency "redcloth"
|
||||
require_dependency "iCal"
|
||||
require 'date'
|
||||
|
||||
$delete_img = "<img src=\"/images/delete.png\" width=\"10\" height=\"10\" />"
|
||||
|
|
|
|||
|
|
@ -32,24 +32,34 @@ class TodoController < ApplicationController
|
|||
# Parameters from form fields should be passed to create new item
|
||||
#
|
||||
def add_item
|
||||
item = Todo.new
|
||||
item.attributes = @params["new_item"]
|
||||
@item = Todo.new
|
||||
@item.attributes = @params["new_item"]
|
||||
|
||||
# Convert the date format entered (as set in config/settings.yml)
|
||||
# to the mysql format YYYY-MM-DD
|
||||
if @params["new_item"]["due"] != ""
|
||||
date_fmt = app_configurations["formats"]["date"]
|
||||
formatted_date = DateTime.strptime(@params["new_item"]["due"], "#{date_fmt}")
|
||||
item.due = formatted_date.strftime("%Y-%m-%d")
|
||||
@item.due = formatted_date.strftime("%Y-%m-%d")
|
||||
else
|
||||
item.due = "0000-00-00"
|
||||
@item.due = "0000-00-00"
|
||||
end
|
||||
|
||||
if item.save
|
||||
# This doesn't seem to be working. No error, but the error
|
||||
# message isn't printed either.
|
||||
unless @item.errors.empty?
|
||||
error_msg = ''
|
||||
@item.errors.each_full do |message|
|
||||
error_msg << message
|
||||
end
|
||||
return error_msg
|
||||
end
|
||||
|
||||
if @item.save
|
||||
flash["confirmation"] = "Next action was successfully added"
|
||||
redirect_to( :action => "list" )
|
||||
else
|
||||
flash["warning"] = "Couldn't add the action"
|
||||
flash["warning"] = "Couldn't add the action because of an error: #{error_msg}"
|
||||
redirect_to( :action => "list" )
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ class Todo < ActiveRecord::Base
|
|||
belongs_to :context
|
||||
belongs_to :project
|
||||
|
||||
# Description field can't be empty, and must be < 100 bytes
|
||||
# Notes must be < 60,000 bytes (65,000 actually, but I'm being cautious)
|
||||
validates_presence_of :description, :message => "no description provided"
|
||||
validates_length_of :description, :maximum => 100, :message => "description is too long"
|
||||
validates_lenght_of :notes, :maximum => 60000, :message => "notes are too long"
|
||||
#validates_format_of :due, :with => /^[\d]{2,2}\/[\d]{2,2}\/[\d]{4,4}$/, :message => "date format incorrect"
|
||||
|
||||
|
||||
def before_save
|
||||
# Add a creation date (Ruby object format) to item before it's saved
|
||||
# if there is no existing creation date (this prevents creation date
|
||||
|
|
@ -13,4 +21,5 @@ class Todo < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@
|
|||
</div><!- End of display_box -->
|
||||
|
||||
<div id="input_box">
|
||||
<h2>Today's events are: </h2>
|
||||
<ul>
|
||||
<% ical = ICal::ICalReader.new("Personal") %>
|
||||
<% ical.todaysEvents do |event| %>
|
||||
<%= puts "<li>#{event.startTime} - #{event.summary}</li>" %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2>Add next action</h2>
|
||||
<form method="post" action="add_item">
|
||||
<label for="new_item_description">Next action</label><br />
|
||||
|
|
@ -83,6 +92,7 @@
|
|||
<br />
|
||||
<input type="submit" value="Add item">
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<% if @flash["confirmation"] %><div class="confirmation"><%= @flash["confirmation"] %></div><% end %>
|
||||
|
|
|
|||
289
tracks/lib/iCal.rb
Normal file
289
tracks/lib/iCal.rb
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
|
||||
require 'date'
|
||||
require 'ParseDate'
|
||||
|
||||
module ICal
|
||||
|
||||
SECONDS_PER_DAY = 24 * 60 * 60
|
||||
|
||||
class ICalReader
|
||||
@@iCalFolder = "/Users/jchappell/Library/Calendars"
|
||||
|
||||
def initialize(calendarName = nil)
|
||||
@events = []
|
||||
@iCalFiles = Dir[@@iCalFolder + "/*.ics"]
|
||||
@iCalFiles.sort! {|x, y| x.downcase <=> y.downcase}
|
||||
if calendarName then
|
||||
fullName = @@iCalFolder + "/" + calendarName + ".ics"
|
||||
@iCalFiles = @iCalFiles.select {|name| name == fullName}
|
||||
end
|
||||
end
|
||||
|
||||
def calendars
|
||||
@iCalFiles.collect {|name| File.basename(name, ".ics")}
|
||||
end
|
||||
|
||||
def readEvents
|
||||
@events = []
|
||||
@iCalFiles.each do |fileName|
|
||||
lines = File.readlines(fileName);
|
||||
inEvent = false
|
||||
eventLines = []
|
||||
lines.each do |line|
|
||||
if line =~ /^BEGIN:VEVENT/ then
|
||||
inEvent = true
|
||||
eventLines = []
|
||||
end
|
||||
|
||||
if inEvent
|
||||
eventLines << line
|
||||
if line =~ /^END:VEVENT/ then
|
||||
inEvent = false
|
||||
@events << parseEvent(eventLines)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@events
|
||||
end
|
||||
|
||||
def parseEvent(lines)
|
||||
event = ICalEvent.new()
|
||||
startDate = nil
|
||||
rule = nil
|
||||
|
||||
lines.each do |line|
|
||||
if line =~ /^SUMMARY:(.*)/ then
|
||||
event.summary = $1
|
||||
elsif line =~ /^DTSTART;.*:(.*).*/ then
|
||||
startDate = parseDate($1)
|
||||
elsif line =~ /^EXDATE.*:(.*)/ then
|
||||
event.addExceptionDate(parseDate($1))
|
||||
elsif line =~ /^RRULE:(.*)/ then
|
||||
rule = $1
|
||||
end
|
||||
end
|
||||
|
||||
event.startDate = startDate
|
||||
event.addRecurrenceRule(rule)
|
||||
event
|
||||
end
|
||||
|
||||
def parseDate(dateStr)
|
||||
# We constrain the year to 1970 because Time won't handle lesser years
|
||||
# If it is less than 1970 then its probably a birthday or something
|
||||
# in which case, we don't really care about the year
|
||||
year = dateStr[0,4].to_i
|
||||
year = 1970 if year < 1970
|
||||
|
||||
month = dateStr[4,2].to_i
|
||||
day = dateStr[6,2].to_i
|
||||
hour = dateStr[9,2].to_i
|
||||
minute = dateStr[11,2].to_i
|
||||
Time.local(year, month, day, hour, minute)
|
||||
end
|
||||
|
||||
def events
|
||||
readEvents if @events == []
|
||||
@events
|
||||
end
|
||||
|
||||
def selectEvents(&predicate)
|
||||
#
|
||||
# The start date of each event could be different due to recurring events
|
||||
# Since we can assume the date is the same for all events, we just compare
|
||||
# the times when sorting
|
||||
#
|
||||
now = Time.now
|
||||
events.select(&predicate).sort do |event1, event2|
|
||||
time1 = Time.local(now.year, now.month, now.day, event1.startDate.hour,
|
||||
event1.startDate.min)
|
||||
time2 = Time.local(now.year, now.month, now.day, event2.startDate.hour,
|
||||
event2.startDate.min)
|
||||
time1 <=> time2
|
||||
end
|
||||
end
|
||||
|
||||
def todaysEvents
|
||||
#events.select {|event| event.startsToday? }
|
||||
selectEvents {|event| event.startsToday? }
|
||||
end
|
||||
|
||||
def tomorrowsEvents
|
||||
#events.select {|event| event.startsTomorrow? }
|
||||
selectEvents {|event| event.startsTomorrow?}
|
||||
end
|
||||
|
||||
def eventsFor(date)
|
||||
#events.select {|event| event.startsOn?(date)}
|
||||
selectEvents {|event| event.startsOn?(date)}
|
||||
end
|
||||
end
|
||||
|
||||
class DateParser
|
||||
# Given a date as a string, returns a Time object
|
||||
def DateParser.parse(dateStr)
|
||||
dateValues = ParseDate::parsedate(dateStr)
|
||||
Time.local(*dateValues[0, 3])
|
||||
end
|
||||
|
||||
def DateParser.format(date)
|
||||
date.strftime("%m/%d/%Y")
|
||||
end
|
||||
end
|
||||
|
||||
class ICalEvent
|
||||
|
||||
def initialize
|
||||
@exceptionDates = []
|
||||
end
|
||||
|
||||
def <=>(otherEvent)
|
||||
return @startDate <=> otherEvent.startDate
|
||||
end
|
||||
|
||||
def addExceptionDate(date)
|
||||
@exceptionDates << date
|
||||
end
|
||||
|
||||
def addRecurrenceRule(rule)
|
||||
@dateSet = DateSet.new(@startDate, rule)
|
||||
end
|
||||
|
||||
def startsToday?
|
||||
startsOn?(Time.now)
|
||||
end
|
||||
|
||||
def startsTomorrow?
|
||||
tomorrow = Time.now + SECONDS_PER_DAY;
|
||||
startsOn?(tomorrow)
|
||||
end
|
||||
|
||||
def startsOn?(date)
|
||||
(startDate.year == date.year and startDate.month == date.month and
|
||||
startDate.day == date.day) or @dateSet.includes?(date)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{@startDate.strftime("%m/%d/%Y (%I:%M %p)")} - #{@summary}"
|
||||
end
|
||||
|
||||
def startTime
|
||||
@startDate
|
||||
end
|
||||
|
||||
attr_accessor :startDate, :summary
|
||||
end
|
||||
|
||||
class DateSet
|
||||
|
||||
def initialize(startDate, rule)
|
||||
@startDate = startDate
|
||||
@frequency = nil
|
||||
@count = nil
|
||||
@untilDate = nil
|
||||
@byMonth = nil
|
||||
@byDay = nil
|
||||
parseRecurrenceRule(rule)
|
||||
end
|
||||
|
||||
def parseRecurrenceRule(rule)
|
||||
|
||||
if rule =~ /FREQ=(.*?);/ then
|
||||
@frequency = $1
|
||||
end
|
||||
|
||||
if rule =~ /COUNT=(\d*)/ then
|
||||
@count = $1.to_i
|
||||
end
|
||||
|
||||
if rule =~ /UNTIL=(.*?);/ then
|
||||
@untilDate = DateParser.parse($1)
|
||||
#puts @untilDate
|
||||
end
|
||||
|
||||
if rule =~ /INTERVAL=(\d*)/ then
|
||||
@interval = $1.to_i
|
||||
end
|
||||
|
||||
if rule =~ /BYMONTH=(.*?);/ then
|
||||
@byMonth = $1
|
||||
end
|
||||
|
||||
if rule =~ /BYDAY=(.*?);/ then
|
||||
@byDay = $1
|
||||
#puts "byDay = #{@byDay}"
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
puts "#<DateSet: starts: #{@startDate.strftime("%m/%d/%Y")}, occurs: #{@frequency}, count: #{@count}, until: #{@until}, byMonth: #{@byMonth}, byDay: #{@byDay}>"
|
||||
end
|
||||
|
||||
def includes?(date)
|
||||
return true if date == @startDate
|
||||
return false if @untilDate and date > @untilDate
|
||||
|
||||
case @frequency
|
||||
when 'DAILY'
|
||||
#if @untilDate then
|
||||
# return (@startDate..@untilDate).include?(date)
|
||||
#end
|
||||
increment = @interval ? @interval : 1
|
||||
d = @startDate
|
||||
counter = 0
|
||||
until d > date
|
||||
|
||||
if @count then
|
||||
counter += 1
|
||||
if counter >= @count
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
d += (increment * SECONDS_PER_DAY)
|
||||
if d.day == date.day and
|
||||
d.year == date.year and
|
||||
d.month == date.month then
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
when 'WEEKLY'
|
||||
return true if @startDate.wday == date.wday
|
||||
|
||||
when 'MONTHLY'
|
||||
|
||||
when 'YEARLY'
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
attr_reader :frequency
|
||||
attr_accessor :startDate
|
||||
end
|
||||
|
||||
if $0 == __FILE__ then
|
||||
#reader = ICalReader.new("Test")
|
||||
reader = ICalReader.new
|
||||
|
||||
puts
|
||||
puts "Today"
|
||||
puts "====="
|
||||
puts reader.todaysEvents
|
||||
|
||||
puts
|
||||
puts "Tomorrow"
|
||||
puts "========"
|
||||
puts reader.tomorrowsEvents
|
||||
|
||||
puts
|
||||
puts "08/14/2003 Events"
|
||||
puts "================="
|
||||
puts reader.eventsFor(Time.local(2003, 8, 14))
|
||||
puts
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -185,7 +185,7 @@ h2 a:hover {
|
|||
border: 1px solid #ED2E38;
|
||||
background-color: #F6979C;
|
||||
padding: 5px;
|
||||
color: #007E00;
|
||||
color: #ED2E38;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue