lita-task-scheduler 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/lib/lita/handlers/task_scheduler.rb +61 -94
- data/lib/lita/scheduler.rb +62 -0
- data/lita-task-scheduler.gemspec +1 -1
- data/spec/lita/handlers/task_scheduler_spec.rb +14 -11
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c87964e095867fd79b29baed0c909bea11c627b8
|
4
|
+
data.tar.gz: c594c7eae944748bcd509e50214814edc629f2fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 407c632bfb744874758e04fa94fdc2af7e69cdea4bbd9d13fdc2ec02c3fb1d25031ced5f2596b1f0e3e5c69b2831d4024247193b2062adb469bbe44147da4658
|
7
|
+
data.tar.gz: 8c25068482aadcee6efbbab58aa09085167a71d09758516f43efcb0537be13d218b8565e379e090ec591372fd3a174a7abd49274ee9384450373a4f9b612e186
|
@@ -1,29 +1,41 @@
|
|
1
|
-
require '
|
2
|
-
require 'json'
|
1
|
+
require 'lita/scheduler'
|
3
2
|
|
4
3
|
module Lita
|
5
4
|
module Handlers
|
6
5
|
class TaskScheduler < Handler
|
7
6
|
|
7
|
+
# START:routes
|
8
8
|
route(/^schedule\s+"(.+)"\s+in\s+(.+)$/i, :schedule_command, command: true)
|
9
9
|
route(/^show schedule$/i, :show_schedule, command: true)
|
10
10
|
route(/^empty schedule$/i, :empty_schedule, command: true)
|
11
|
+
# END:routes
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
# START:handlers
|
14
14
|
def show_schedule(payload)
|
15
|
-
|
16
|
-
|
17
|
-
payload.reply schedule_report(schedule)
|
15
|
+
payload.reply schedule_report(scheduler.get_all)
|
18
16
|
end
|
19
17
|
|
20
18
|
def empty_schedule(payload)
|
21
|
-
|
19
|
+
scheduler.clear
|
20
|
+
show_schedule payload
|
21
|
+
end
|
22
|
+
|
23
|
+
# START:schedule_command
|
24
|
+
def schedule_command(payload)
|
25
|
+
task, timing = payload.matches.last
|
26
|
+
run_at = parse_timing(timing)
|
27
|
+
serialized = command_to_hash(payload.message, new_body: task)
|
28
|
+
|
29
|
+
defer_task(serialized, run_at)
|
22
30
|
show_schedule payload
|
23
31
|
end
|
32
|
+
# END:schedule_command
|
33
|
+
|
34
|
+
def scheduler
|
35
|
+
@_schedule ||= Scheduler.new(redis: redis, logger: Lita.logger)
|
36
|
+
end
|
24
37
|
|
25
38
|
def schedule_report(schedule)
|
26
|
-
reply = 'Scheduled tasks: '
|
27
39
|
descriptions = []
|
28
40
|
|
29
41
|
schedule.keys.each do |timestamp|
|
@@ -36,75 +48,16 @@ module Lita
|
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
def schedule_command(payload)
|
43
|
-
task, timing = payload.matches.last
|
44
|
-
run_at = parse_timing(timing)
|
45
|
-
serialized = serialize_message(payload.message, new_body: task)
|
46
|
-
|
47
|
-
defer_task(serialized, run_at)
|
48
|
-
show_schedule(payload)
|
51
|
+
'Scheduled tasks: ' + (descriptions.empty? ? 'None.' : descriptions.join)
|
49
52
|
end
|
50
53
|
|
54
|
+
# START:defer_task
|
51
55
|
def defer_task(serialized_task, run_at)
|
52
|
-
|
53
|
-
|
54
|
-
redis.watch(REDIS_TASKS_KEY)
|
55
|
-
|
56
|
-
tasks = redis.hget(REDIS_TASKS_KEY, key_time) || []
|
57
|
-
|
58
|
-
tasks = JSON.parse(tasks) unless tasks.empty?
|
59
|
-
tasks << serialized_task
|
60
|
-
|
61
|
-
redis.hset(REDIS_TASKS_KEY, key_time, tasks.to_json)
|
62
|
-
|
63
|
-
redis.unwatch
|
64
|
-
|
65
|
-
tasks
|
66
|
-
end
|
67
|
-
|
68
|
-
def execute_tasks(serialized_tasks)
|
69
|
-
serialized_tasks.each do |serialized_task|
|
70
|
-
Lita.logger.debug "Resending task #{serialized_task}"
|
71
|
-
resend serialized_task
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def run_loop
|
76
|
-
Thread.new do
|
77
|
-
loop do
|
78
|
-
tick
|
79
|
-
sleep 1
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def tick
|
85
|
-
tasks = find_tasks_due
|
86
|
-
tasks.each { |t| resend t }
|
87
|
-
Lita.logger.debug "Task loop done for #{Time.now}"
|
88
|
-
end
|
89
|
-
|
90
|
-
def find_tasks_due
|
91
|
-
results = []
|
92
|
-
timestamps = redis.hkeys(REDIS_TASKS_KEY)
|
93
|
-
|
94
|
-
timestamps.each do |t|
|
95
|
-
key_time = Time.at(t.to_i)
|
96
|
-
next unless key_time <= Time.now
|
97
|
-
|
98
|
-
tasks_raw = redis.hget(REDIS_TASKS_KEY, t)
|
99
|
-
tasks = JSON.parse(tasks_raw)
|
100
|
-
|
101
|
-
results += tasks
|
102
|
-
redis.hdel(REDIS_TASKS_KEY, t)
|
103
|
-
end
|
104
|
-
|
105
|
-
results
|
56
|
+
scheduler.add(serialized_task, run_at)
|
106
57
|
end
|
58
|
+
# END:defer_task
|
107
59
|
|
60
|
+
# START:parse_timing
|
108
61
|
def parse_timing(timing)
|
109
62
|
count, unit = timing.split
|
110
63
|
count = count.to_i
|
@@ -125,22 +78,14 @@ module Lita
|
|
125
78
|
|
126
79
|
Time.now.utc + seconds
|
127
80
|
end
|
81
|
+
# END:parse_timing
|
128
82
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
redis.set(key, serialized.to_json)
|
134
|
-
reloaded = JSON.parse redis.get(key), symbolize_names: true
|
135
|
-
|
136
|
-
resend(reloaded)
|
137
|
-
end
|
138
|
-
|
139
|
-
def resend(serialized)
|
140
|
-
user = Lita::User.new(serialized.fetch('user_name'))
|
141
|
-
room = Lita::Room.new(serialized.fetch('room_name'))
|
83
|
+
# START:resend_command
|
84
|
+
def resend_command(command_hash)
|
85
|
+
user = Lita::User.new(command_hash.fetch('user_name'))
|
86
|
+
room = Lita::Room.new(command_hash.fetch('room_name'))
|
142
87
|
source = Lita::Source.new(user: user, room: room)
|
143
|
-
body = "#{robot.name} #{
|
88
|
+
body = "#{robot.name} #{command_hash.fetch('body')}"
|
144
89
|
|
145
90
|
newmsg = Lita::Message.new(
|
146
91
|
robot,
|
@@ -150,20 +95,42 @@ module Lita
|
|
150
95
|
|
151
96
|
robot.receive newmsg
|
152
97
|
end
|
98
|
+
# END:resend_command
|
153
99
|
|
154
|
-
|
100
|
+
# START:serialize_message
|
101
|
+
def command_to_hash(command, new_body: nil)
|
155
102
|
{
|
156
|
-
user_name:
|
157
|
-
room_name:
|
158
|
-
body: new_body ||
|
103
|
+
user_name: command.user.name,
|
104
|
+
room_name: command.source.room,
|
105
|
+
body: new_body || command.body
|
159
106
|
}
|
160
107
|
end
|
108
|
+
# END:serialize_message
|
161
109
|
|
162
|
-
|
110
|
+
def find_tasks_due
|
111
|
+
scheduler.find_tasks_due
|
112
|
+
end
|
113
|
+
|
114
|
+
# START:loop_ticks
|
115
|
+
def run_loop
|
116
|
+
Thread.new do
|
117
|
+
loop do
|
118
|
+
tick
|
119
|
+
sleep 1
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
163
123
|
|
164
|
-
|
165
|
-
|
124
|
+
def tick
|
125
|
+
tasks = find_tasks_due
|
126
|
+
tasks.each { |t| resend_command t }
|
127
|
+
Lita.logger.debug "Task loop done for #{Time.now}"
|
166
128
|
end
|
129
|
+
|
130
|
+
on(:loaded) { run_loop }
|
131
|
+
# END:loop_ticks
|
132
|
+
|
133
|
+
Lita.register_handler(self)
|
167
134
|
end
|
168
135
|
end
|
169
136
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
# START:preamble
|
4
|
+
module Lita
|
5
|
+
class Scheduler
|
6
|
+
REDIS_TASKS_KEY = name.to_s
|
7
|
+
|
8
|
+
def initialize(redis:, logger:)
|
9
|
+
@redis = redis
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :redis, :logger
|
14
|
+
|
15
|
+
def get_all
|
16
|
+
redis.hgetall(REDIS_TASKS_KEY)
|
17
|
+
end
|
18
|
+
# END:preamble
|
19
|
+
|
20
|
+
# START:store_new
|
21
|
+
def add(payload, timestamp)
|
22
|
+
key_time = timestamp.to_i.to_s
|
23
|
+
|
24
|
+
redis.watch(REDIS_TASKS_KEY)
|
25
|
+
|
26
|
+
tasks = redis.hget(REDIS_TASKS_KEY, key_time) || []
|
27
|
+
|
28
|
+
tasks = JSON.parse(tasks) unless tasks.empty?
|
29
|
+
tasks << payload
|
30
|
+
|
31
|
+
redis.hset(REDIS_TASKS_KEY, key_time, tasks.to_json)
|
32
|
+
|
33
|
+
redis.unwatch
|
34
|
+
tasks
|
35
|
+
end
|
36
|
+
# END:store_new
|
37
|
+
|
38
|
+
def clear
|
39
|
+
redis.del(REDIS_TASKS_KEY)
|
40
|
+
end
|
41
|
+
|
42
|
+
# START:find_tasks_due
|
43
|
+
def find_tasks_due
|
44
|
+
results = []
|
45
|
+
timestamps = redis.hkeys(REDIS_TASKS_KEY)
|
46
|
+
|
47
|
+
timestamps.each do |t|
|
48
|
+
key_time = Time.at(t.to_i)
|
49
|
+
next unless key_time <= Time.now
|
50
|
+
|
51
|
+
tasks_raw = redis.hget(REDIS_TASKS_KEY, t)
|
52
|
+
tasks = JSON.parse(tasks_raw)
|
53
|
+
|
54
|
+
results += tasks
|
55
|
+
redis.hdel(REDIS_TASKS_KEY, t)
|
56
|
+
end
|
57
|
+
|
58
|
+
results
|
59
|
+
end
|
60
|
+
# END:find_tasks_due
|
61
|
+
end
|
62
|
+
end
|
data/lita-task-scheduler.gemspec
CHANGED
@@ -2,13 +2,17 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Lita::Handlers::TaskScheduler, lita_handler: true do
|
4
4
|
let(:robot) { Lita::Robot.new(registry) }
|
5
|
+
before(:suite) { subject.scheduler.clear }
|
5
6
|
|
6
7
|
subject { described_class.new(robot) }
|
7
8
|
|
9
|
+
# START:routes
|
8
10
|
describe 'routing' do
|
9
|
-
it { is_expected.to route('Lita schedule "
|
11
|
+
it { is_expected.to route('Lita schedule "double 4" in 2 hours') }
|
10
12
|
it { is_expected.to route('Lita show schedule') }
|
13
|
+
it { is_expected.to route('Lita empty schedule') }
|
11
14
|
end
|
15
|
+
# END:routes
|
12
16
|
|
13
17
|
describe 'functionality' do
|
14
18
|
|
@@ -27,6 +31,7 @@ describe Lita::Handlers::TaskScheduler, lita_handler: true do
|
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
34
|
+
# START: defer_task
|
30
35
|
describe ':defer_task' do
|
31
36
|
it 'defers any single task' do
|
32
37
|
message = { canary_message: Time.now }
|
@@ -48,7 +53,9 @@ describe Lita::Handlers::TaskScheduler, lita_handler: true do
|
|
48
53
|
expect(result).to eq([message] * 6)
|
49
54
|
end
|
50
55
|
end
|
56
|
+
# END: defer_task
|
51
57
|
|
58
|
+
# START:find_tasks_due
|
52
59
|
describe ':find_tasks_due' do
|
53
60
|
context 'two tasks are scheduled for five seconds ago' do
|
54
61
|
before { 2.times { subject.defer_task('past_task', Time.now - 5) } }
|
@@ -69,28 +76,23 @@ describe Lita::Handlers::TaskScheduler, lita_handler: true do
|
|
69
76
|
end
|
70
77
|
end
|
71
78
|
end
|
79
|
+
# END:find_tasks_due
|
72
80
|
end
|
73
81
|
|
74
|
-
|
75
|
-
it 'resends each task' do
|
76
|
-
tasks = [{}, {}]
|
77
|
-
|
78
|
-
expect(subject).to receive(:resend).exactly(2).times
|
79
|
-
subject.execute_tasks(tasks)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
82
|
+
# START:loop_ticks
|
83
83
|
describe 'tick' do
|
84
84
|
before { subject.stub(:find_tasks_due).and_return ['a_task'] }
|
85
85
|
|
86
86
|
it 'should find tasks due and resend them' do
|
87
87
|
expect(subject).to receive(:find_tasks_due)
|
88
|
-
expect(subject).to receive(:
|
88
|
+
expect(subject).to receive(:resend_command).with('a_task')
|
89
89
|
|
90
90
|
subject.tick
|
91
91
|
end
|
92
92
|
end
|
93
|
+
# END:loop_ticks
|
93
94
|
|
95
|
+
# START:parse_timing
|
94
96
|
describe ':parse_timing' do
|
95
97
|
def time_drift(time, expected_seconds:)
|
96
98
|
delta = (Time.now.utc - time).abs
|
@@ -112,4 +114,5 @@ describe Lita::Handlers::TaskScheduler, lita_handler: true do
|
|
112
114
|
end
|
113
115
|
end
|
114
116
|
end
|
117
|
+
# END:parse_timing
|
115
118
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lita-task-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel J. Pritchett
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04
|
11
|
+
date: 2018-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lita
|
@@ -121,6 +121,7 @@ files:
|
|
121
121
|
- Rakefile
|
122
122
|
- lib/lita-task-scheduler.rb
|
123
123
|
- lib/lita/handlers/task_scheduler.rb
|
124
|
+
- lib/lita/scheduler.rb
|
124
125
|
- lita-task-scheduler.gemspec
|
125
126
|
- locales/en.yml
|
126
127
|
- spec/lita/handlers/task_scheduler_spec.rb
|