Skip to content
Browse files

wip

  • Loading branch information...
1 parent f6e9521 commit 7842d09f22d739543a52b6da829613de31089302 @txus committed Aug 29, 2011
View
28 Readme.md
@@ -51,13 +51,10 @@ The clients will receive notifications via sound/growl (configurable in a
If you're not using OSX, try to install the `afplay` program manually or...
send a patch to make it work with your OS :)
-* The pomodoro schedule is (as of v0.0.3) hard-coded. It starts at 8:00 AM,
- stops at 13:00 for lunch, and starts again at 13:20. In the following
- versions this will be freely configurable.
-
-
## Configuration
+### Client configuration
+
By default, both sound and visual notifications are displayed on each event.
If you want to configure this, create a file in your home directory named
`.domodororc` with some YAML configuration:
@@ -66,8 +63,25 @@ If you want to configure this, create a file in your home directory named
$ echo "visual: true" >> ~/.domodororc
$ echo "sound: false" >> ~/.domodororc
-## Copyright
+### Server configuration
-Copyright (c) 2011 Josep M. Bach. Released under the MIT license.
+Inside the server machine, to configure the pomodoro schedule, create a
+`~/.domodororc` file with YAML configuration (the values provided below are
+those that will be used by default if you don't specify them):
+````yaml
+server:
+ pomodoro_duration: 25 # in minutes
+ pomodoro_break: 5 # in minutes
+ long_break_after: 4 # pomodoros
+ day_start: "08:30"
+ day_end: "16:30"
+
+ lunch_time: "13:00"
+ lunch_duration: 30 # in minutes
+````
+
+## Copyright
+
+Copyright (c) 2011 Josep M. Bach. Released under the MIT license.
View
2 lib/domodoro.rb
@@ -1,6 +1,8 @@
require 'eventmachine'
require "domodoro/version"
+require "domodoro/schedule"
+require "domodoro/timepoint"
require "domodoro/config"
require "domodoro/channel"
require "domodoro/server"
View
34 lib/domodoro/channel.rb
@@ -1,6 +1,6 @@
module Domodoro
class Channel < EM::Channel
- def broadcast(hour, min)
+ def broadcast(timestamp, schedule)
if ENV['DEBUG']
puts 'DEBUG MODE: Start on even minutes, stop on odd minutes'
if min % 2 == 0
@@ -13,31 +13,13 @@ def broadcast(hour, min)
return
end
- if (hour >= 8 && hour < 13)
- morning(min)
- elsif (hour >= 13 && min >= 20) &&
- afternoon(min)
- end
- end
- def morning(min)
- case min
- when 0, 30
- puts "#{Time.now} - Starting pomodoro!"
- self << :start
- when 25, 55
- puts "#{Time.now} - Pomodoro break!"
- self << :stop
- end
- end
-
- def afternoon(min)
- case min
- when 20, 50
- puts "#{Time.now} - Starting pomodoro!"
- self << :start
- when 45, 15
- puts "#{Time.now} - Pomodoro break!"
- self << :stop
+ action = schedule.to_hash[timestamp]
+ if action
+ next_action = schedule.action_after(timestamp)
+ self << {
+ :action => action,
+ :next_action => next_action
+ }
end
end
end
View
77 lib/domodoro/client.rb
@@ -12,34 +12,49 @@ def connected=(value)
@connected = true
end
+ def next_action
+ @next_action
+ end
+
+ def next_action=(action)
+ @next_action = action
+ end
+
def start(host, port='9111')
- Config.load
+ Config.load_client_configuration
puts "#{Time.now} - Domodoro listening on #{host}:#{port}"
puts "Visual notifications: #{Config.visual}"
puts "Sound notifications: #{Config.sound}\n"
EM.run do
EM.connect(host, port) do |c|
- c.extend EM::P::LineText2
+ c.extend EM::P::ObjectProtocol
def c.connection_completed
puts " - Connected to server!"
Client.connected = true
end
- def c.receive_line(line)
- case line
- when /start/
- puts " - Starting pomodoro!"
- Client.work
- when /stop/
- puts " - Pomodoro break!"
- Client.break
+ def c.receive_object(object)
+ case object[:current_action].last
+ when :start
+ puts "[#{object[:current_action]}] - Starting pomodoro!"
+ Client.work
+ when :stop
+ puts "[#{object[:current_action]}] - Pomodoro break!"
+ Client.break
+ when :lunch
+ puts "[#{object[:current_action]}] - Lunch time!"
+ Client.lunch
+ when :go_home
+ puts "[#{object[:current_action]}] - Time to go home!"
+ Client.home
end
+ Client.next_action = object[:next_action]
end
end
EM.add_periodic_timer(1) do
EM.next_tick do
if Client.connected
- print_time
+ print_next_action
else
puts 'Cannot connect to server. Is it running?'
end
@@ -71,20 +86,48 @@ def break
end
end
+ def lunch
+ EM.defer do
+ if Config.visual
+ Notify.notify "Domodoro", "Lunch time!"
+ end
+ if Config.sound
+ system("afplay #{path_to('lunch.wav')}")
+ end
+ end
+ end
+
+ def home
+ EM.defer do
+ if Config.visual
+ Notify.notify "Domodoro", "Time to go home!"
+ end
+ if Config.sound
+ system("afplay #{path_to('home.wav')}")
+ end
+ end
+ end
+
private
def path_to(asset)
File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'assets', asset))
end
- def print_time
- hour = Time.now.hour.to_s.rjust(2, '0')
- min = Time.now.min.to_s.rjust(2, '0')
- secs = Time.now.sec.to_s.rjust(2, '0')
+ def print_next_action
+ timestamp = Timepoint.new(Client.next_action.first)
+ action = case Client.next_action.last
+ when :start then "Pomodoro"
+ when :stop then "Pomodoro Break"
+ when :lunch then "Lunch time"
+ when :go_home then "Go home"
+
+ time_left = timestamp.left_until
+
$stdout.print "\r"
- $stdout.print " " * 20
+ $stdout.print " " * 50
$stdout.print "\r"
- $stdout.print "#{hour}:#{min}:#{secs}"
+ $stdout.print "Next: #{action} in #{time_left}"
$stdout.flush
end
View
33 lib/domodoro/config.rb
@@ -1,11 +1,12 @@
require 'yaml'
+require 'ostruct'
module Domodoro
module Config
attr_accessor :visual, :sound
extend self
- def load
+ def load_client_configuration
if File.exist?(File.expand_path('~/.domodororc'))
file = YAML.load(File.read(File.expand_path('~/.domodororc')))
@@ -16,5 +17,35 @@ def load
self.sound = true
end
end
+
+ def get_server_configuration
+ config = OpenStruct.new
+
+ if File.exist?(File.expand_path('~/.domodororc'))
+ file = YAML.load(File.read(File.expand_path('~/.domodororc')))['server']
+
+ config.pomodoro_duration = file['pomodoro_duration']
+ config.pomodoro_break = file['pomodoro_break']
+ config.long_break_after = file['long_break_after']
+
+ config.day_start = Timepoint.new(file['day_start'])
+ config.day_end = Timepoint.new(file['day_end'])
+
+ config.lunch_time = Timepoint.new(file['lunch_time'])
+ config.lunch_duration = file['lunch_duration']
+ end
+
+ config.pomodoro_duration ||= 25
+ config.pomodoro_break ||= 5
+ config.long_break_after ||= 4
+
+ config.day_start ||= Timepoint.new('08:30')
+ config.day_end ||= Timepoint.new('16:30')
+
+ config.lunch_time ||= Timepoint.new('13:00')
+ config.lunch_duration ||= 30
+
+ config
+ end
end
end
View
82 lib/domodoro/schedule.rb
@@ -0,0 +1,82 @@
+module Domodoro
+ class Schedule
+ def initialize
+ @times = {}
+ @config = Config.get_server_configuration
+ end
+
+ def generate!
+ # Key points
+ @times[@config.lunch_time.to_s] = :lunch
+ @times[@config.day_end.to_s] = :go_home
+
+
+
+ generate_pomodoros_between(@config.day_start, @config.lunch_time)
+ generate_pomodoros_between(@config.lunch_time + @config.lunch_duration, @config.day_end)
+ end
+
+ def to_hash
+ @times
+ end
+
+ def current_action(timestamp)
+ times = @times.dup
+
+ if times[timestamp]
+ minus_one = false
+ else
+ minus_one = true
+ times[timestamp] = :now
+ end
+
+ sorted = times.sort
+
+ idx = sorted.index do |element|
+ element == sorted.assoc(timestamp)
+ end
+
+ idx = idx - 1 if minus_one
+
+ sorted[idx]
+ end
+
+ def action_after(timestamp)
+ times = @times.dup
+ times[timestamp] ||= :now
+ sorted = times.sort
+
+ idx = sorted.index do |element|
+ element == sorted.assoc(timestamp)
+ end
+
+ sorted[idx + 1]
+ end
+
+ private
+
+ def generate_pomodoros_between(start, finish)
+ duration = @config.pomodoro_duration
+ pomodoro_break = @config.pomodoro_break
+
+ time = start
+ long_break_yet = 1
+
+ while time.before?(finish)
+ @times[time.to_s] = :start
+ time += duration
+
+ break unless time.before?(finish)
+
+ @times[time.to_s] = :stop
+
+ break_rest = pomodoro_break
+ break_rest *= 2 if long_break_yet == @config.long_break_after
+
+ time += break_rest
+
+ long_break_yet += 1
+ end
+ end
+ end
+end
View
24 lib/domodoro/server.rb
@@ -1,18 +1,23 @@
module Domodoro
class Server < EM::Connection
+ include EM::P::ObjectProtocol
+
attr_reader :channel
class << self
def start(host='0.0.0.0', port='9111')
+ schedule = Schedule.new
+ schedule.generate!
+
puts "#{Time.now} - Domodoro serving at #{host}:#{port}"
EM.run do
channel = Channel.new
- EM.start_server(host, port, self, channel)
+ EM.start_server(host, port, self, channel, schedule)
EM.add_periodic_timer(1) do
if Time.now.sec == 0
- channel.broadcast(Time.now.hour, Time.now.min)
+ channel.broadcast(timestamp, schedule)
else
EM.next_tick do
print_time
@@ -32,14 +37,25 @@ def print_time
$stdout.print "#{hour}:#{min}:#{secs}"
$stdout.flush
end
+
+ def timestamp
+ hour = Time.now.hour.to_s.rjust(2, '0')
+ min = Time.now.min.to_s.rjust(2, '0')
+ [hour, min].join(':')
+ end
end
- def initialize(channel)
+ def initialize(channel, schedule)
@channel = channel
+ @schedule = schedule
end
def post_init
- @sid = channel.subscribe { |m| send_data "#{m.inspect}\n" }
+ @sid = channel.subscribe { |m| send_object(m) }
+ send_object({
+ :current_action => @schedule.current_action(Server.timestamp),
+ :next_action => @schedule.action_after(Server.timestamp)
+ })
end
def unbind
View
68 lib/domodoro/timepoint.rb
@@ -0,0 +1,68 @@
+module Domodoro
+ class Timepoint
+ attr_reader :hour, :min
+
+ def initialize(*args)
+ case args.first
+ when String
+ @hour, @min = args.first.split(':').map(&:to_i)
+ when Fixnum
+ @hour, @min = args.first, args.last
+ end
+ end
+
+ def +(minutes)
+ hours_to_advance = ((@min + minutes) / 60).to_i
+
+ h = @hour + hours_to_advance
+ m = (@min + minutes) % 60
+
+ Timepoint.new("#{h}:#{m}")
+ end
+
+ def after?(timepoint)
+ if @hour > timepoint.hour
+ true
+ elsif @hour == timepoint.hour
+ @min >= timepoint.min
+ else
+ false
+ end
+ end
+
+ def before?(timepoint)
+ if @hour < timepoint.hour
+ true
+ elsif @hour == timepoint.hour
+ @min < timepoint.min
+ else
+ false
+ end
+ end
+
+ def ==(timepoint)
+ case timepoint
+ when String
+ Timepoint.new(timepoint) == self
+ when Timepoint
+ @hour == timepoint.hour && @min == timepoint.min
+ end
+ end
+
+ def to_s
+ [@hour, @min].map(&:to_s).map do |number|
+ number.rjust(2, '0')
+ end.join(':')
+ end
+
+ def left_until(timestamp)
+ hours_to_reduce = (60 / @min - minutes).to_i
+
+ h = @hour - hours_to_reduce
+ m = (@min - minutes) % 60
+ s = 60 - Time.now.sec
+
+ "#{h}:#{m}:#{s}"
+ end
+ end
+end
View
117 test/domodoro/channel_test.rb
@@ -11,123 +11,28 @@ module Domodoro
end
describe 'broadcast' do
- describe 'in the morning' do
- it 'broadcasts from 8:00 until 12:59' do
- @channel.expects(:morning).with(5).never
- @channel.expects(:morning).with(10).once
- @channel.expects(:morning).with(15).once
- @channel.expects(:morning).with(20).once
- @channel.expects(:morning).with(25).once
- @channel.expects(:morning).with(30).once
- @channel.expects(:morning).with(35).never
-
- min = 0
- (7..13).each do |hour|
- min += 5
- @channel.broadcast(hour, min)
- end
- end
- end
-
- describe 'in the afternoon' do
- it 'broadcasts from 13:20 on' do
- @channel.expects(:afternoon).with(5).never
- @channel.expects(:afternoon).with(10).never
- @channel.expects(:afternoon).with(15).never
- @channel.expects(:afternoon).with(20).once
- @channel.expects(:afternoon).with(25).once
- @channel.expects(:afternoon).with(30).once
- @channel.expects(:afternoon).with(35).once
-
- min = 0
- (13..19).each do |hour|
- min += 5
- @channel.broadcast(hour, min)
- end
- end
+ before do
+ @schedule = {'08:30' => :start, '09:00' => :stop}
end
- it 'does not broadcast during lunch time' do
- %w(morning afternoon).each do |timespan|
- @channel.expects(timespan).with(5).never
- end
-
- (0..19).each do |min|
- @channel.broadcast(13, min)
- end
- end
- end
-
- describe '#morning' do
- describe 'pomodoro starts' do
- it 'broadcasts a :start message when the time is XX:00' do
- @channel.expects(:<<).with(:start)
- @channel.morning 0
- end
-
- it 'broadcasts a :start message when the time is XX:30' do
+ describe 'if theres an action for the timestamp' do
+ it 'broadcasts it' do
@channel.expects(:<<).with(:start)
- @channel.morning 30
- end
- end
-
- describe 'pomodoro stops' do
- it 'broadcasts a :stop message when the time is XX:25' do
@channel.expects(:<<).with(:stop)
- @channel.morning 25
- end
- it 'broadcasts a :stop message when the time is XX:55' do
- @channel.expects(:<<).with(:stop)
- @channel.morning 55
- end
- end
-
- it 'does not broadcast anything at other times' do
- @channel.expects(:<<).never
-
- @channel.morning 5
- @channel.morning 10
- @channel.morning 20
- @channel.morning 40
- @channel.morning 50
- end
- end
-
- describe '#afternoon' do
- describe 'pomodoro starts' do
- it 'broadcasts a :start message when the time is XX:20' do
- @channel.expects(:<<).with(:start)
- @channel.afternoon 20
- end
-
- it 'broadcasts a :start message when the time is XX:50' do
- @channel.expects(:<<).with(:start)
- @channel.afternoon 50
+ @channel.broadcast("08:30", @schedule)
+ @channel.broadcast("09:00", @schedule)
end
end
- describe 'pomodoro stops' do
- it 'broadcasts a :stop message when the time is XX:45' do
- @channel.expects(:<<).with(:stop)
- @channel.afternoon 45
- end
+ describe 'otherwise' do
+ it 'does not' do
+ @channel.expects(:<<).never
- it 'broadcasts a :stop message when the time is XX:15' do
- @channel.expects(:<<).with(:stop)
- @channel.afternoon 15
+ @channel.broadcast("07:30", @schedule)
+ @channel.broadcast("08:31", @schedule)
end
end
-
- it 'does not broadcast anything at other times' do
- @channel.expects(:<<).never
-
- @channel.afternoon 5
- @channel.afternoon 10
- @channel.afternoon 25
- @channel.afternoon 40
- @channel.afternoon 55
- end
end
end
end
View
4 test/domodoro/config_test.rb
@@ -9,7 +9,7 @@ module Domodoro
visual: true
sound: false
"""
- Domodoro::Config.load
+ Domodoro::Config.load_client_configuration
assert_equal true, Domodoro::Config.visual
assert_equal false, Domodoro::Config.sound
@@ -20,7 +20,7 @@ module Domodoro
it 'sets both options to true' do
File.stubs(:exist?).returns false
- Domodoro::Config.load
+ Domodoro::Config.load_client_configuration
assert_equal true, Domodoro::Config.visual
assert_equal true, Domodoro::Config.sound
View
108 test/domodoro/schedule_test.rb
@@ -0,0 +1,108 @@
+require 'test_helper'
+require 'ostruct'
+require 'pp'
+
+module Domodoro
+ describe Schedule do
+ before do
+ File.stubs(:exist?).returns false
+ @schedule = Schedule.new
+ @schedule.generate!
+ @times = @schedule.to_hash
+ end
+
+ describe "before lunch" do
+ it 'generates start times correctly' do
+ %w(08:30 09:00 09:30 10:00 10:35 11:05 11:35 12:05 12:35).each do |time|
+ assert_equal :start, @times[time]
+ end
+ end
+
+ it 'generates stop times correctly' do
+ %w(08:55 09:25 09:55 10:25 11:00 11:30 12:00 12:30).each do |time|
+ assert_equal :stop, @times[time]
+ end
+ assert_equal :lunch, @times["13:00"]
+ end
+ end
+
+ describe 'after lunch' do
+ it 'generates start times correctly' do
+ %w(13:30 14:00 14:30 15:00 15:35 16:05).each do |time|
+ assert_equal :start, @times[time]
+ end
+ end
+
+ it 'generates stop times correctly' do
+ %w(13:55 14:25 14:55 15:25 16:00).each do |time|
+ assert_equal :stop, @times[time]
+ end
+ assert_equal :go_home, @times["16:30"]
+ end
+ end
+
+ describe 'uneven, weird schedules' do
+ before do
+ config = OpenStruct.new
+ config.pomodoro_duration = 25
+ config.pomodoro_break = 5
+ config.long_break_after = 4
+ config.day_start = Timepoint.new('9:13')
+ config.lunch_time = Timepoint.new('12:49')
+ config.lunch_duration = 120
+ config.day_end = Timepoint.new('15:00')
+
+ Config.expects(:get_server_configuration).returns config
+
+ @schedule = Schedule.new
+ @schedule.generate!
+ @times = @schedule.to_hash
+ end
+
+ it 'respect lunch' do
+ assert_equal :lunch, @times["12:49"]
+ assert_equal :start, @times["14:49"]
+ end
+
+ it 'respect home' do
+ assert_equal :go_home, @times["15:00"]
+ end
+ end
+
+ describe '#action_after' do
+ it 'returns the next action after a given timestamp' do
+ times = {
+ "08:30" => :start,
+ "09:15" => :stop,
+ "10:15" => :start,
+ }
+
+ @schedule = Schedule.new
+ @schedule.instance_variable_set(:@times, times)
+
+ @schedule.action_after("08:30").must_equal ['09:15', :stop]
+ @schedule.action_after("08:45").must_equal ['09:15', :stop]
+ @schedule.action_after("09:31").must_equal ['10:15', :start]
+ end
+ end
+
+ describe '#current_action' do
+ it 'returns the current or past action on a given timestamp' do
+ times = {
+ "08:30" => :start,
+ "09:15" => :stop,
+ "10:15" => :start,
+ }
+
+ @schedule = Schedule.new
+ @schedule.instance_variable_set(:@times, times)
+
+ @schedule.current_action("08:30").must_equal ['08:30', :start]
+ @schedule.current_action("08:31").must_equal ['08:30', :start]
+ @schedule.current_action("09:18").must_equal ['09:15', :stop]
+ @schedule.current_action("12:18").must_equal ['10:15', :start]
+ end
+ end
+
+ end
+end
View
44 test/domodoro/server_test.rb
@@ -6,26 +6,23 @@ module Domodoro
before do
@channel = Channel.new
- end
-
- it 'initializes with a channel' do
- server = Domodoro::Server.new(nil, @channel)
- assert_equal @channel, server.channel
+ @schedule = Schedule.new
+ @schedule.generate!
end
it 'repeats every message broadcasted to the channel' do
- server = Server.new(nil, @channel)
- EM.start_server('127.0.0.1', 8888, Server, :app => server)
+ EM.start_server('127.0.0.1', 8888, Server, @channel, @schedule)
- server.expects(:send_data).with(":start\n")
+ Server.any_instance.expects(:send_data).with(":start\n")
@channel << :start
end
describe '.start' do
it 'opens a server with a new channel' do
Channel.expects(:new).returns @channel
- EM.expects(:start_server).with('0.0.0.0', '8888', Server, @channel)
+ Schedule.expects(:new).returns @schedule
+ EM.expects(:start_server).with('0.0.0.0', '8888', Server, @channel, @schedule)
Server.start('0.0.0.0', '8888')
end
@@ -61,5 +58,34 @@ def @channel.broadcast(*args)
end
end
+ describe 'on connection' do
+ it 'sends the current and next actions to the client', :timeout => 0.2 do
+ Server.stubs(:timestamp).returns "13:15"
+
+ EM.start_server('127.0.0.1', 8888, Server, @channel, @schedule)
+
+ FakeSocketClient = Class.new(EM::Connection) do
+ include EM::P::ObjectProtocol
+ @@object = nil
+ def receive_object(object)
+ @@object = object
+ end
+ end
+
+ EM.connect('127.0.0.1', 8888, FakeSocketClient)
+
+ EM.add_timer(0.1) do
+ object = FakeSocketClient.class_variable_get(:@@object)
+
+ assert_equal ["13:00", :lunch], object[:current_action]
+ assert_equal ["13:30", :start], object[:next_action]
+
+ done!
+ end
+
+ wait!
+ end
+ end
+
end
end
View
87 test/domodoro/timepoint_test.rb
@@ -0,0 +1,87 @@
+require 'test_helper'
+
+module Domodoro
+ describe Timepoint do
+ before do
+ @timepoint = Timepoint.new("08:30")
+ end
+
+ it 'initializes with an hour/minute timestamp' do
+ assert_equal 8, @timepoint.hour
+ assert_equal 30, @timepoint.min
+ end
+
+ it 'initializes with hours and minutes as fixnums' do
+ timepoint = Timepoint.new(8, 30)
+ assert_equal 8, timepoint.hour
+ assert_equal 30, timepoint.min
+ end
+
+ describe '#to_s' do
+ it 'returns the original timestamp' do
+ assert_equal "08:30", @timepoint.to_s
+ end
+ end
+
+ describe '+' do
+ describe 'when the added minutes belong to the next hour' do
+ it 'adds time appropriately returning a new timepoint' do
+ new_timepoint = @timepoint + 45
+
+ assert_equal 9, new_timepoint.hour
+ assert_equal 15, new_timepoint.min
+ end
+ end
+
+ describe 'within the same hour' do
+ it 'works the same way' do
+ new_timepoint = @timepoint + 15
+
+ assert_equal 8, new_timepoint.hour
+ assert_equal 45, new_timepoint.min
+ end
+ end
+ end
+
+ describe '#after?' do
+ before do
+ @timepoint = Timepoint.new(8, 30)
+ end
+ it 'tells if the timepoint is after another timepoint' do
+ Timepoint.new(8, 45).after?(@timepoint).must_equal true
+ Timepoint.new(9, 15).after?(@timepoint).must_equal true
+ Timepoint.new(8, 25).after?(@timepoint).must_equal false
+ Timepoint.new(7, 10).after?(@timepoint).must_equal false
+ end
+ end
+
+ describe '#before?' do
+ before do
+ @timepoint = Timepoint.new(17, 30)
+ end
+ it 'tells if the timepoint is before another timepoint' do
+ Timepoint.new(17, 15).before?(@timepoint).must_equal true
+ Timepoint.new(13, 00).before?(@timepoint).must_equal true
+ Timepoint.new(17, 40).before?(@timepoint).must_equal false
+ Timepoint.new(19, 10).before?(@timepoint).must_equal false
+ end
+ end
+
+ describe '#==' do
+ before do
+ @timepoint = Timepoint.new(8, 30)
+ end
+ it 'tells if the timepoint equals another timepoint' do
+ Timepoint.new(8, 30).must_equal @timepoint
+ Timepoint.new(8, 45).wont_equal @timepoint
+ Timepoint.new(8, 15).wont_equal @timepoint
+ end
+
+ it 'tells if the timepoint equals another string' do
+ "08:30".must_equal @timepoint
+ "08:45".wont_equal @timepoint
+ "08:15".wont_equal @timepoint
+ end
+ end
+ end
+end

0 comments on commit 7842d09

Please sign in to comment.
Something went wrong with that request. Please try again.