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 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