domodoro 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|