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 +21 -6
- data/assets/{stop.wav → break.wav} +0 -0
- data/assets/home.wav +0 -0
- data/assets/lunch.wav +0 -0
- data/assets/{start.wav → work.wav} +0 -0
- data/lib/domodoro.rb +2 -0
- data/lib/domodoro/channel.rb +8 -38
- data/lib/domodoro/client.rb +73 -34
- data/lib/domodoro/config.rb +32 -1
- data/lib/domodoro/schedule.rb +82 -0
- data/lib/domodoro/server.rb +20 -4
- data/lib/domodoro/timepoint.rb +80 -0
- data/lib/domodoro/version.rb +1 -1
- data/test/domodoro/channel_test.rb +23 -108
- data/test/domodoro/client_test.rb +65 -129
- data/test/domodoro/config_test.rb +2 -2
- data/test/domodoro/schedule_test.rb +108 -0
- data/test/domodoro/server_test.rb +39 -9
- data/test/domodoro/timepoint_test.rb +105 -0
- metadata +102 -68
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
|
data/assets/home.wav
ADDED
Binary file
|
data/assets/lunch.wav
ADDED
Binary file
|
File without changes
|
data/lib/domodoro.rb
CHANGED
data/lib/domodoro/channel.rb
CHANGED
@@ -1,43 +1,13 @@
|
|
1
1
|
module Domodoro
|
2
2
|
class Channel < EM::Channel
|
3
|
-
def broadcast(
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
data/lib/domodoro/client.rb
CHANGED
@@ -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.
|
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::
|
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.
|
29
|
-
case
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
81
|
-
|
82
|
-
|
83
|
-
|
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 " " *
|
123
|
+
$stdout.print " " * 100
|
86
124
|
$stdout.print "\r"
|
87
|
-
$stdout.print "#{
|
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
|
|
data/lib/domodoro/config.rb
CHANGED
@@ -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
|
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
|
data/lib/domodoro/server.rb
CHANGED
@@ -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(
|
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|
|
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
|