domodoro 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Readme.md CHANGED
@@ -51,13 +51,10 @@ The clients will receive notifications via sound/growl (configurable in a
51
51
  If you're not using OSX, try to install the `afplay` program manually or...
52
52
  send a patch to make it work with your OS :)
53
53
 
54
- * The pomodoro schedule is (as of v0.0.3) hard-coded. It starts at 8:00 AM,
55
- stops at 13:00 for lunch, and starts again at 13:20. In the following
56
- versions this will be freely configurable.
57
-
58
-
59
54
  ## Configuration
60
55
 
56
+ ### Client configuration
57
+
61
58
  By default, both sound and visual notifications are displayed on each event.
62
59
  If you want to configure this, create a file in your home directory named
63
60
  `.domodororc` with some YAML configuration:
@@ -66,8 +63,26 @@ If you want to configure this, create a file in your home directory named
66
63
  $ echo "visual: true" >> ~/.domodororc
67
64
  $ echo "sound: false" >> ~/.domodororc
68
65
 
66
+ ### Server configuration
67
+
68
+ Inside the server machine, to configure the pomodoro schedule, create a
69
+ `~/.domodororc` file with YAML configuration (the values provided below are
70
+ those that will be used by default if you don't specify them):
71
+
72
+ ````yaml
73
+ server:
74
+ pomodoro_duration: 25 # in minutes
75
+ pomodoro_break: 5 # in minutes
76
+ long_break_after: 4 # pomodoros
77
+
78
+ day_start: "08:30"
79
+ day_end: "16:30"
80
+
81
+ lunch_time: "13:00"
82
+ lunch_duration: 30 # in minutes
83
+ ````
84
+
69
85
  ## Copyright
70
86
 
71
87
  Copyright (c) 2011 Josep M. Bach. Released under the MIT license.
72
88
 
73
-
File without changes
Binary file
Binary file
File without changes
@@ -1,6 +1,8 @@
1
1
  require 'eventmachine'
2
2
 
3
3
  require "domodoro/version"
4
+ require "domodoro/schedule"
5
+ require "domodoro/timepoint"
4
6
  require "domodoro/config"
5
7
  require "domodoro/channel"
6
8
  require "domodoro/server"
@@ -1,43 +1,13 @@
1
1
  module Domodoro
2
2
  class Channel < EM::Channel
3
- def broadcast(hour, min)
4
- if ENV['DEBUG']
5
- puts 'DEBUG MODE: Start on even minutes, stop on odd minutes'
6
- if min % 2 == 0
7
- puts "#{Time.now} - Starting pomodoro!"
8
- self << :start
9
- else
10
- puts "#{Time.now} - Pomodoro break!"
11
- self << :stop
12
- end
13
- return
14
- end
15
-
16
- if (hour >= 8 && hour < 13)
17
- morning(min)
18
- elsif (hour >= 13 && min >= 20) &&
19
- afternoon(min)
20
- end
21
- end
22
- def morning(min)
23
- case min
24
- when 0, 30
25
- puts "#{Time.now} - Starting pomodoro!"
26
- self << :start
27
- when 25, 55
28
- puts "#{Time.now} - Pomodoro break!"
29
- self << :stop
30
- end
31
- end
32
-
33
- def afternoon(min)
34
- case min
35
- when 20, 50
36
- puts "#{Time.now} - Starting pomodoro!"
37
- self << :start
38
- when 45, 15
39
- puts "#{Time.now} - Pomodoro break!"
40
- self << :stop
3
+ def broadcast(timestamp, schedule)
4
+ action = schedule.to_hash[timestamp]
5
+ if action
6
+ next_action = schedule.action_after(timestamp)
7
+ self << {
8
+ :current_action => [timestamp, action],
9
+ :next_action => next_action
10
+ }
41
11
  end
42
12
  end
43
13
  end
@@ -12,34 +12,55 @@ module Domodoro
12
12
  @connected = true
13
13
  end
14
14
 
15
+ def current_action
16
+ @current_action
17
+ end
18
+
19
+ def current_action=(action)
20
+ @current_action = action
21
+ end
22
+
23
+ def next_action
24
+ @next_action
25
+ end
26
+
27
+ def next_action=(action)
28
+ @next_action = action
29
+ end
30
+
15
31
  def start(host, port='9111')
16
- Config.load
32
+ Config.load_client_configuration
17
33
  puts "#{Time.now} - Domodoro listening on #{host}:#{port}"
18
34
  puts "Visual notifications: #{Config.visual}"
19
35
  puts "Sound notifications: #{Config.sound}\n"
20
36
 
21
37
  EM.run do
22
38
  EM.connect(host, port) do |c|
23
- c.extend EM::P::LineText2
39
+ c.extend EM::P::ObjectProtocol
24
40
  def c.connection_completed
25
41
  puts " - Connected to server!"
26
42
  Client.connected = true
27
43
  end
28
- def c.receive_line(line)
29
- case line
30
- when /start/
31
- puts " - Starting pomodoro!"
32
- Client.work
33
- when /stop/
34
- puts " - Pomodoro break!"
35
- Client.break
44
+ def c.receive_object(object)
45
+ case object[:current_action].last
46
+ when :start
47
+ Client.work
48
+ when :stop
49
+ Client.break
50
+ when :lunch
51
+ Client.lunch
52
+ when :go_home
53
+ Client.home
36
54
  end
55
+ Client.current_action = object[:current_action]
56
+ Client.next_action = object[:next_action]
57
+ puts
37
58
  end
38
59
  end
39
60
  EM.add_periodic_timer(1) do
40
61
  EM.next_tick do
41
- if Client.connected
42
- print_time
62
+ if Client.connected && Client.current_action
63
+ print_status
43
64
  else
44
65
  puts 'Cannot connect to server. Is it running?'
45
66
  end
@@ -49,42 +70,60 @@ module Domodoro
49
70
 
50
71
  end
51
72
 
52
- def work
53
- EM.defer do
54
- if Config.visual
55
- Notify.notify "Domodoro", "Time to work!"
56
- end
57
- if Config.sound
58
- system("afplay #{path_to('start.wav')}")
73
+ %w(work break lunch home).each do |action|
74
+ define_method(action) do
75
+ EM.defer do
76
+ if Config.visual
77
+ Notify.notify "Domodoro", message_for(action)
78
+ end
79
+ if Config.sound
80
+ system("afplay #{path_to("#{action}.wav")}")
81
+ end
59
82
  end
60
83
  end
61
84
  end
62
85
 
63
- def break
64
- EM.defer do
65
- if Config.visual
66
- Notify.notify "Domodoro", "Take a 5 min. break."
67
- end
68
- if Config.sound
69
- system("afplay #{path_to('stop.wav')}")
70
- end
86
+ private
87
+
88
+ def message_for(action)
89
+ case action.to_s
90
+ when "work" then "Time to work!"
91
+ when "break" then "Time to take a little break."
92
+ when "lunch" then "Lunch time!"
93
+ when "home" then "Ok folks, time to go home!"
71
94
  end
72
95
  end
73
96
 
74
- private
97
+ def name_for(action)
98
+ case action.to_s
99
+ when "start" then "Pomodoro"
100
+ when "stop" then "Pomodoro Break"
101
+ when "lunch" then "Lunch time"
102
+ when "go_home" then "Go home"
103
+ end
104
+ end
75
105
 
76
106
  def path_to(asset)
77
107
  File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'assets', asset))
78
108
  end
79
109
 
80
- def print_time
81
- hour = Time.now.hour.to_s.rjust(2, '0')
82
- min = Time.now.min.to_s.rjust(2, '0')
83
- secs = Time.now.sec.to_s.rjust(2, '0')
110
+ def print_status
111
+ current_action = name_for(Client.current_action.last)
112
+
113
+ if Client.next_action
114
+ next_action = name_for(Client.next_action.last)
115
+
116
+ # Calculate time left until next action
117
+ now = Timepoint.new(Time.now.hour, Time.now.min)
118
+ timestamp = Timepoint.new(Client.next_action.first)
119
+ time_left = now.left_until(timestamp)
120
+ end
121
+
84
122
  $stdout.print "\r"
85
- $stdout.print " " * 20
123
+ $stdout.print " " * 100
86
124
  $stdout.print "\r"
87
- $stdout.print "#{hour}:#{min}:#{secs}"
125
+ $stdout.print "[ #{[Client.current_action.first, current_action].join(' | ')} ]"
126
+ $stdout.print " - Next: #{next_action} in #{time_left}" if Client.next_action
88
127
  $stdout.flush
89
128
  end
90
129
 
@@ -1,11 +1,12 @@
1
1
  require 'yaml'
2
+ require 'ostruct'
2
3
 
3
4
  module Domodoro
4
5
  module Config
5
6
  attr_accessor :visual, :sound
6
7
  extend self
7
8
 
8
- def load
9
+ def load_client_configuration
9
10
  if File.exist?(File.expand_path('~/.domodororc'))
10
11
  file = YAML.load(File.read(File.expand_path('~/.domodororc')))
11
12
 
@@ -16,5 +17,35 @@ module Domodoro
16
17
  self.sound = true
17
18
  end
18
19
  end
20
+
21
+ def get_server_configuration
22
+ config = OpenStruct.new
23
+
24
+ if File.exist?(File.expand_path('~/.domodororc'))
25
+ file = YAML.load(File.read(File.expand_path('~/.domodororc')))['server']
26
+
27
+ config.pomodoro_duration = file['pomodoro_duration']
28
+ config.pomodoro_break = file['pomodoro_break']
29
+ config.long_break_after = file['long_break_after']
30
+
31
+ config.day_start = Timepoint.new(file['day_start'])
32
+ config.day_end = Timepoint.new(file['day_end'])
33
+
34
+ config.lunch_time = Timepoint.new(file['lunch_time'])
35
+ config.lunch_duration = file['lunch_duration']
36
+ end
37
+
38
+ config.pomodoro_duration ||= 25
39
+ config.pomodoro_break ||= 5
40
+ config.long_break_after ||= 4
41
+
42
+ config.day_start ||= Timepoint.new('08:30')
43
+ config.day_end ||= Timepoint.new('16:30')
44
+
45
+ config.lunch_time ||= Timepoint.new('13:00')
46
+ config.lunch_duration ||= 30
47
+
48
+ config
49
+ end
19
50
  end
20
51
  end
@@ -0,0 +1,82 @@
1
+ module Domodoro
2
+ class Schedule
3
+ def initialize
4
+ @times = {}
5
+ @config = Config.get_server_configuration
6
+ end
7
+
8
+ def generate!
9
+ # Key points
10
+ @times[@config.lunch_time.to_s] = :lunch
11
+ @times[@config.day_end.to_s] = :go_home
12
+
13
+
14
+
15
+ generate_pomodoros_between(@config.day_start, @config.lunch_time)
16
+ generate_pomodoros_between(@config.lunch_time + @config.lunch_duration, @config.day_end)
17
+ end
18
+
19
+ def to_hash
20
+ @times
21
+ end
22
+
23
+ def current_action(timestamp)
24
+ times = @times.dup
25
+
26
+ if times[timestamp]
27
+ minus_one = false
28
+ else
29
+ minus_one = true
30
+ times[timestamp] = :now
31
+ end
32
+
33
+ sorted = times.sort
34
+
35
+ idx = sorted.index do |element|
36
+ element == sorted.assoc(timestamp)
37
+ end
38
+
39
+ idx = idx - 1 if minus_one
40
+
41
+ sorted[idx]
42
+ end
43
+
44
+ def action_after(timestamp)
45
+ times = @times.dup
46
+ times[timestamp] ||= :now
47
+ sorted = times.sort
48
+
49
+ idx = sorted.index do |element|
50
+ element == sorted.assoc(timestamp)
51
+ end
52
+
53
+ sorted[idx + 1]
54
+ end
55
+
56
+ private
57
+
58
+ def generate_pomodoros_between(start, finish)
59
+ duration = @config.pomodoro_duration
60
+ pomodoro_break = @config.pomodoro_break
61
+
62
+ time = start
63
+ long_break_yet = 1
64
+
65
+ while time.before?(finish)
66
+ @times[time.to_s] = :start
67
+ time += duration
68
+
69
+ break unless time.before?(finish)
70
+
71
+ @times[time.to_s] = :stop
72
+
73
+ break_rest = pomodoro_break
74
+ break_rest *= 2 if long_break_yet == @config.long_break_after
75
+
76
+ time += break_rest
77
+
78
+ long_break_yet += 1
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,18 +1,23 @@
1
1
  module Domodoro
2
2
  class Server < EM::Connection
3
+ include EM::P::ObjectProtocol
4
+
3
5
  attr_reader :channel
4
6
 
5
7
  class << self
6
8
  def start(host='0.0.0.0', port='9111')
9
+ schedule = Schedule.new
10
+ schedule.generate!
11
+
7
12
  puts "#{Time.now} - Domodoro serving at #{host}:#{port}"
8
13
  EM.run do
9
14
  channel = Channel.new
10
15
 
11
- EM.start_server(host, port, self, channel)
16
+ EM.start_server(host, port, self, channel, schedule)
12
17
 
13
18
  EM.add_periodic_timer(1) do
14
19
  if Time.now.sec == 0
15
- channel.broadcast(Time.now.hour, Time.now.min)
20
+ channel.broadcast(timestamp, schedule)
16
21
  else
17
22
  EM.next_tick do
18
23
  print_time
@@ -32,14 +37,25 @@ module Domodoro
32
37
  $stdout.print "#{hour}:#{min}:#{secs}"
33
38
  $stdout.flush
34
39
  end
40
+
41
+ def timestamp
42
+ hour = Time.now.hour.to_s.rjust(2, '0')
43
+ min = Time.now.min.to_s.rjust(2, '0')
44
+ [hour, min].join(':')
45
+ end
35
46
  end
36
47
 
37
- def initialize(channel)
48
+ def initialize(channel, schedule)
38
49
  @channel = channel
50
+ @schedule = schedule
39
51
  end
40
52
 
41
53
  def post_init
42
- @sid = channel.subscribe { |m| send_data "#{m.inspect}\n" }
54
+ @sid = channel.subscribe { |m| send_object(m) }
55
+ send_object({
56
+ :current_action => @schedule.current_action(Server.timestamp),
57
+ :next_action => @schedule.action_after(Server.timestamp)
58
+ })
43
59
  end
44
60
 
45
61
  def unbind