lita-task-scheduler 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c1c534abd255cca4223c3d12ac9fed05cbb065a2
4
- data.tar.gz: 4bc8e533d727d056ee1430a2c1354cfa9aeec931
3
+ metadata.gz: 23f66b45e3100b728a6a53a23fa9f166552d7989
4
+ data.tar.gz: 4b23ad6c9ed0638905152bb6ac960fa98978a47d
5
5
  SHA512:
6
- metadata.gz: 7701aaef31efb81503dc8eb0f6e4505d8429335757e0b3cbd118747bf6ac8d48aed92a195731b621cdfcc797f7f185b5b68c8741c9a836dffbd4e05d1dbc00db
7
- data.tar.gz: a865d1c5ddc0cab514cdfbc2b219e43a3979619ab88b741736627c0d1265c796ce62bdfbd51ae088ed2ec9bff28c2485ff8ab9d5a2f54c05b47f61b823a582a2
6
+ metadata.gz: 468d8b5438c5d815e570d72d14c727b093f7d584104017c459b1c0a72d2ec6097fba77df538f2e02acfccb7e1615c3b7ab130419845363dbd23ffcc12aff929d
7
+ data.tar.gz: 969dc2317064912351a8bdf4af531fd0e1095186d4100298587b298c0cf5c027339672e2311ad08481dad6757b178ec8f4df5d897e5dbd54ee54739f4acb6c6e
@@ -1,20 +1,135 @@
1
+ require 'pry'
2
+ require 'json'
3
+
1
4
  module Lita
2
5
  module Handlers
3
6
  class TaskScheduler < Handler
4
7
 
5
- route /^schedule\s+"(.+)"\s+in\s+(.+)$/i, :schedule
8
+ route(/^schedule\s+"(.+)"\s+in\s+(.+)$/i, :schedule_command, command: true)
9
+ route(/^show schedule$/i, :show_schedule, command: true)
10
+ route(/^empty schedule$/i, :empty_schedule, command: true)
11
+
12
+ REDIS_TASKS_KEY = name.to_s
13
+
14
+ def show_schedule(payload)
15
+ schedule = redis.hgetall(REDIS_TASKS_KEY)
16
+
17
+ payload.reply schedule_report(schedule)
18
+ end
19
+
20
+ def empty_schedule(payload)
21
+ redis.del(REDIS_TASKS_KEY)
22
+ show_schedule payload
23
+ end
24
+
25
+ def schedule_report(schedule)
26
+ reply = 'Scheduled tasks: '
27
+ descriptions = []
28
+
29
+ schedule.keys.each do |timestamp|
30
+ play_time = Time.at(timestamp.to_i)
31
+ tasks_json = schedule[timestamp]
32
+ tasks = JSON.parse(tasks_json)
33
+
34
+ tasks.each do |task|
35
+ descriptions << "\n - \"#{task.fetch('body')}\" at #{play_time}"
36
+ end
37
+ end
38
+
39
+ reply + (descriptions.empty? ? 'None.' : descriptions.join)
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)
49
+ end
50
+
51
+ def defer_task(serialized_task, run_at)
52
+ key_time = run_at.to_i.to_s
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)
6
62
 
7
- def schedule(payload)
8
- payload.matches.each do |task, timing|
9
- serialized = serialize_message(payload.message, new_body: task)
10
- resend(serialized)
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
11
81
  end
12
82
  end
13
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
106
+ end
107
+
108
+ def parse_timing(timing)
109
+ count, unit = timing.split
110
+ count = count.to_i
111
+ unit = unit.downcase.strip.gsub(/s$/, '')
112
+
113
+ seconds = case unit
114
+ when 'second'
115
+ count
116
+ when 'minute'
117
+ count * 60
118
+ when 'hour'
119
+ count * 60 * 60
120
+ when 'day'
121
+ count * 60 * 60 * 24
122
+ else
123
+ raise ArgumentError, "I don't recognize #{unit}"
124
+ end
125
+
126
+ Time.now.utc + seconds
127
+ end
128
+
14
129
  def rebroadcast(payload)
15
130
  serialized = serialize_message(payload.message)
16
131
 
17
- key = "delay_#{rand(100..10000)}"
132
+ key = "delay_#{rand(100..10_000)}"
18
133
  redis.set(key, serialized.to_json)
19
134
  reloaded = JSON.parse redis.get(key), symbolize_names: true
20
135
 
@@ -22,10 +137,10 @@ module Lita
22
137
  end
23
138
 
24
139
  def resend(serialized)
25
- user = Lita::User.new(serialized.fetch(:user_name))
26
- room = Lita::Room.new(serialized.fetch(:room_name))
140
+ user = Lita::User.new(serialized.fetch('user_name'))
141
+ room = Lita::Room.new(serialized.fetch('room_name'))
27
142
  source = Lita::Source.new(user: user, room: room)
28
- body = "#{robot.name} #{serialized.fetch(:body)}"
143
+ body = "#{robot.name} #{serialized.fetch('body')}"
29
144
 
30
145
  newmsg = Lita::Message.new(
31
146
  robot,
@@ -45,6 +160,10 @@ module Lita
45
160
  end
46
161
 
47
162
  Lita.register_handler(self)
163
+
164
+ on :loaded do
165
+ run_loop
166
+ end
48
167
  end
49
168
  end
50
169
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "lita-task-scheduler"
3
- spec.version = "0.1.1"
3
+ spec.version = "1.0.0"
4
4
  spec.authors = ["Daniel J. Pritchett"]
5
5
  spec.email = ["dpritchett@gmail.com"]
6
6
  spec.description = "Schedule jobs in Lita"
@@ -1,4 +1,115 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe Lita::Handlers::TaskScheduler, lita_handler: true do
4
+ let(:robot) { Lita::Robot.new(registry) }
5
+
6
+ subject { described_class.new(robot) }
7
+
8
+ describe 'routing' do
9
+ it { is_expected.to route('Lita schedule "show schedule" in 2 hours') }
10
+ it { is_expected.to route('Lita show schedule') }
11
+ end
12
+
13
+ describe 'functionality' do
14
+
15
+ describe ':schedule_report' do
16
+ context 'tasks are scheduled'
17
+ before do
18
+ send_message 'Lita schedule "show schedule" in 1 seconds'
19
+ send_message 'Lita schedule "show schedule" in 6 seconds'
20
+ end
21
+
22
+ it 'should list scheduled tasks on demand' do
23
+ message = double 'message'
24
+ expect(message).to receive(:reply)
25
+ subject.show_schedule(message)
26
+ send_message 'Lita show schedule'
27
+ end
28
+ end
29
+
30
+ describe ':defer_task' do
31
+ it 'defers any single task' do
32
+ message = { canary_message: Time.now }
33
+ run_at = Time.now + 5
34
+ result = subject.defer_task(message, run_at)
35
+ expect(result).to include(message)
36
+ end
37
+
38
+ it 'stores multiple same-second tasks in an array' do
39
+ message = { 'canary_message' => Time.now.to_i }
40
+ run_at = Time.now + 5
41
+
42
+ 5.times do
43
+ subject.defer_task(message, run_at)
44
+ end
45
+
46
+ result = subject.defer_task(message, run_at)
47
+
48
+ expect(result).to eq([message] * 6)
49
+ end
50
+ end
51
+
52
+ describe ':find_tasks_due' do
53
+ context 'two tasks are scheduled for five seconds ago' do
54
+ before { 2.times { subject.defer_task('past_task', Time.now - 5) } }
55
+
56
+ it 'returns all past due tasks' do
57
+ result = subject.find_tasks_due
58
+ expected = %w[past_task past_task]
59
+ expect(result).to eq(expected)
60
+ end
61
+ end
62
+
63
+ context 'one task scheduled in the future' do
64
+ before { subject.defer_task('future_task', Time.now + 100) }
65
+
66
+ it 'does not return that new task' do
67
+ result = subject.find_tasks_due
68
+ expect(result).to_not include('future_task')
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ describe 'execute_tasks' do
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
+
83
+ describe 'tick' do
84
+ before { subject.stub(:find_tasks_due).and_return ['a_task'] }
85
+
86
+ it 'should find tasks due and resend them' do
87
+ expect(subject).to receive(:find_tasks_due)
88
+ expect(subject).to receive(:resend).with('a_task')
89
+
90
+ subject.tick
91
+ end
92
+ end
93
+
94
+ describe ':parse_timing' do
95
+ def time_drift(time, expected_seconds:)
96
+ delta = (Time.now.utc - time).abs
97
+ (expected_seconds - delta).abs
98
+ end
99
+
100
+ it 'parses seconds, minutes, hours, and days' do
101
+ test_table = [
102
+ ['3 seconds', 3],
103
+ ['4 minutes', 4 * 60],
104
+ ['2 hours', 2 * 60 * 60],
105
+ ['5 days', 5 * 60 * 60 * 24]
106
+ ]
107
+
108
+ test_table.each do |input, expected|
109
+ response = subject.parse_timing(input)
110
+ drift = time_drift(response, expected_seconds: expected)
111
+ expect(drift < 0.1).to be_truthy, "#{input}:\t #{expected}"
112
+ end
113
+ end
114
+ end
4
115
  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: 0.1.1
4
+ version: 1.0.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-03-14 00:00:00.000000000 Z
11
+ date: 2018-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lita