resque_manager 3.3.4 → 3.3.5
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/app/controllers/resque_manager/resque_controller.rb +244 -242
- data/lib/resque_manager/overrides/resque/worker.rb +20 -57
- data/lib/resque_manager/overrides/resque_status/status.rb +3 -2
- data/lib/resque_manager/recipes.rb +2 -2
- data/lib/resque_manager/version.rb +1 -1
- data/lib/tasks/worker.rake +1 -1
- data/test/dummy/config/application.rb +0 -1
- data/test/dummy/log/test.log +5575 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/0eddc19d46318e2e286cc171ae4cc73e +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/0fa6e3c14356aa527d68a8d56fa37f28 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/1efd074fd1074b3dc88145b480ff961f +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/60ffef45ddefd5c7746d17977fff0717 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/67f1ef70e7ede542318b8d55e25b16c3 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/73d6fa09352cb76ac81e1683e832b93f +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/74e5ba1cca7a1470d53c54fb60368b78 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/8ed12e4193473760f95b973567a8c206 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/97119b908ebed2633edfd00ac90d9011 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/989465d3ea8575dd0b54981a9e8add38 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/a26f2ae225aa4b87c462d540c7cf43f9 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/b2e622954654f415590723e9b882063e +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/c387148880e015d1eab0dc838b326022 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/d91a5918f5aa43a43c8135a67c78e989 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/e1207380d69eeee3284e02636c26f24a +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/e227278d3c65d8aa1159da720263f771 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/ea4f3c726fc1046cad1ad243faf84e7d +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/ec164819553e2e5b28f1efc9bd970978 +0 -0
- data/test/functional/resque_manager/resque_controller_test.rb +2 -2
- data/test/unit/overrides/resque_status/status_test.rb +3 -3
- metadata +52 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 266bb0448d58c29fa6aa53def87bbdfeda2af3e9
|
4
|
+
data.tar.gz: 5c2247ff48d7a8b077952f0b18d1a9b26076ffdf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01382eccaa0fe394eec1c2c22ed39d2f7370d3a577d334eca90875a495be463e0012312d3401d63b2642d95d2f9506f94690188084acfabb3211751c6fb0433d
|
7
|
+
data.tar.gz: 86f9e6cffadfdb4ec49d3e4f7541cdd11dbab9156165bbe40ec3a5cc743ea8d430f553bfb2004cb620f9b2b05efff9cdff6da79cbbcd02c811eb01094f1b23d6
|
@@ -5,313 +5,315 @@ unless defined?($rails_rake_task) && $rails_rake_task
|
|
5
5
|
require 'digest/sha1'
|
6
6
|
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
module ResqueManager
|
9
|
+
class ResqueController < ApplicationController
|
10
|
+
unloadable(self) #needed to prevent errors with authenticated system in dev env.
|
10
11
|
|
11
|
-
|
12
|
+
layout 'resque_manager/application'
|
12
13
|
|
13
|
-
|
14
|
+
before_filter :check_connection
|
14
15
|
|
15
|
-
|
16
|
+
before_filter :get_cleaner, :only => [:cleaner, :cleaner_exec, :cleaner_list, :cleaner_stale, :cleaner_dump]
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def queues
|
22
|
-
render('_queues', :locals => {:partial => nil})
|
23
|
-
end
|
18
|
+
def working
|
19
|
+
render('_working')
|
20
|
+
end
|
24
21
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
22
|
+
def queues
|
23
|
+
render('_queues', :locals => {:partial => nil})
|
24
|
+
end
|
29
25
|
|
30
|
-
|
31
|
-
|
26
|
+
def poll
|
27
|
+
@polling = true
|
28
|
+
render(:text => (render_to_string(:action => "#{params[:page]}", :formats => [:html], :layout => false, :resque => Resque)).gsub(/\s{1,}/, ' '))
|
29
|
+
end
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
@statuses = Resque::Plugins::Status::Hash.statuses(@start, @end)
|
36
|
-
@size = Resque::Plugins::Status::Hash.status_ids.size
|
31
|
+
def status_poll
|
32
|
+
@polling = true
|
37
33
|
|
38
|
-
|
39
|
-
|
34
|
+
@start = params[:start].to_i
|
35
|
+
@end = @start + (params[:per_page] || 20)
|
36
|
+
@statuses = Resque::Plugins::Status::Hash.statuses(@start, @end) rescue []
|
37
|
+
@size = Resque::Plugins::Status::Hash.status_ids.size
|
40
38
|
|
41
|
-
|
42
|
-
# We can only dequeue a job when that job is in the same application as the UI.
|
43
|
-
# Otherwise we get an error when we try to constantize a class that does not exist
|
44
|
-
# in the application the UI is in.
|
45
|
-
if ResqueManager.applications.blank?
|
46
|
-
Resque.dequeue(params['class'].constantize, *Resque.decode(params['args']))
|
39
|
+
render(:text => (render_to_string(:action => 'statuses', :formats => [:html], :layout => false)))
|
47
40
|
end
|
48
|
-
redirect_to request.referrer
|
49
|
-
end
|
50
|
-
|
51
|
-
def stop_worker
|
52
|
-
worker = find_worker(params[:worker])
|
53
|
-
worker.quit if worker
|
54
|
-
redirect_to workers_resque_path
|
55
|
-
end
|
56
41
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
42
|
+
def remove_job
|
43
|
+
# We can only dequeue a job when that job is in the same application as the UI.
|
44
|
+
# Otherwise we get an error when we try to constantize a class that does not exist
|
45
|
+
# in the application the UI is in.
|
46
|
+
if ResqueManager.applications.blank?
|
47
|
+
Resque.dequeue(params['class'].constantize, *Resque.decode(params['args']))
|
48
|
+
end
|
49
|
+
redirect_to request.referrer
|
50
|
+
end
|
62
51
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
52
|
+
def stop_worker
|
53
|
+
worker = find_worker(params[:worker])
|
54
|
+
worker.quit if worker
|
55
|
+
redirect_to workers_resque_path
|
56
|
+
end
|
68
57
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
58
|
+
def pause_worker
|
59
|
+
worker = find_worker(params[:worker])
|
60
|
+
worker.pause if worker
|
61
|
+
redirect_to workers_resque_path
|
62
|
+
end
|
74
63
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
64
|
+
def continue_worker
|
65
|
+
worker = find_worker(params[:worker])
|
66
|
+
worker.continue if worker
|
67
|
+
redirect_to workers_resque_path
|
68
|
+
end
|
79
69
|
|
80
|
-
|
81
|
-
|
82
|
-
|
70
|
+
def restart_worker
|
71
|
+
worker = find_worker(params[:worker])
|
72
|
+
worker.restart if worker
|
73
|
+
redirect_to workers_resque_path
|
83
74
|
end
|
84
75
|
|
85
|
-
|
86
|
-
|
76
|
+
def start_worker
|
77
|
+
Resque::Worker.start(params)
|
78
|
+
redirect_to workers_resque_path
|
79
|
+
end
|
87
80
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
stats << "resque.failed+=#{info[:failed]}"
|
92
|
-
stats << "resque.workers=#{info[:workers]}"
|
93
|
-
stats << "resque.working=#{info[:working]}"
|
94
|
-
Resque.queues.each do |queue|
|
95
|
-
stats << "queues.#{queue}=#{Resque.size(queue)}"
|
81
|
+
def stats
|
82
|
+
unless params[:id]
|
83
|
+
redirect_to(stats_resque_path(:id => 'resque'))
|
96
84
|
end
|
97
85
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
86
|
+
if params[:id] == 'txt'
|
87
|
+
info = Resque.info
|
88
|
+
|
89
|
+
stats = []
|
90
|
+
stats << "resque.pending=#{info[:pending]}"
|
91
|
+
stats << "resque.processed+=#{info[:processed]}"
|
92
|
+
stats << "resque.failed+=#{info[:failed]}"
|
93
|
+
stats << "resque.workers=#{info[:workers]}"
|
94
|
+
stats << "resque.working=#{info[:working]}"
|
95
|
+
Resque.queues.each do |queue|
|
96
|
+
stats << "queues.#{queue}=#{Resque.size(queue)}"
|
97
|
+
end
|
103
98
|
|
104
|
-
|
105
|
-
|
106
|
-
|
99
|
+
render(:text => stats.join("</br>").html_safe)
|
100
|
+
end
|
101
|
+
end
|
107
102
|
|
108
|
-
|
109
|
-
config = Resque.schedule[params['job_name']]
|
110
|
-
Resque::Scheduler.enqueue_from_config(config)
|
111
|
-
redirect_to overview_resque_path
|
112
|
-
end
|
103
|
+
# resque-scheduler actions
|
113
104
|
|
114
|
-
|
115
|
-
|
116
|
-
if Resque.schedule.keys.include?(params[:name])
|
117
|
-
errors << 'Name already exists.'
|
118
|
-
end
|
119
|
-
if params[:ip].blank?
|
120
|
-
errors << 'You must enter an ip address for the server you want this job to run on.'
|
105
|
+
def schedule
|
106
|
+
@farm_status = ResqueScheduler.farm_status
|
121
107
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
'ip' => params['ip'],
|
128
|
-
'cron' => params['cron'],
|
129
|
-
'args' => Resque.decode(params['args'].blank? ? nil : params['args']),
|
130
|
-
'description' => params['description']}
|
131
|
-
}
|
132
|
-
Resque.redis.rpush(:scheduled, Resque.encode(config))
|
133
|
-
ResqueScheduler.restart(params['ip'])
|
134
|
-
else
|
135
|
-
flash[:error] = errors.join('<br>').html_safe
|
108
|
+
|
109
|
+
def schedule_requeue
|
110
|
+
config = Resque.schedule[params['job_name']]
|
111
|
+
Resque::Scheduler.enqueue_from_config(config)
|
112
|
+
redirect_to overview_resque_path
|
136
113
|
end
|
137
|
-
redirect_to schedule_resque_path
|
138
|
-
end
|
139
114
|
|
140
|
-
|
141
|
-
|
142
|
-
if
|
143
|
-
|
144
|
-
|
115
|
+
def add_scheduled_job
|
116
|
+
errors = []
|
117
|
+
if Resque.schedule.keys.include?(params[:name])
|
118
|
+
errors << 'Name already exists.'
|
119
|
+
end
|
120
|
+
if params[:ip].blank?
|
121
|
+
errors << 'You must enter an ip address for the server you want this job to run on.'
|
122
|
+
end
|
123
|
+
if params[:cron].blank?
|
124
|
+
errors << 'You must enter the cron schedule.'
|
125
|
+
end
|
126
|
+
if errors.blank?
|
127
|
+
config = {params['name'] => {'class' => params['class'],
|
128
|
+
'ip' => params['ip'],
|
129
|
+
'cron' => params['cron'],
|
130
|
+
'args' => Resque.decode(params['args'].blank? ? nil : params['args']),
|
131
|
+
'description' => params['description']}
|
132
|
+
}
|
133
|
+
Resque.redis.rpush(:scheduled, Resque.encode(config))
|
145
134
|
ResqueScheduler.restart(params['ip'])
|
135
|
+
else
|
136
|
+
flash[:error] = errors.join('<br>').html_safe
|
146
137
|
end
|
138
|
+
redirect_to schedule_resque_path
|
147
139
|
end
|
148
|
-
redirect_to schedule_resque_path
|
149
|
-
end
|
150
140
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
141
|
+
def remove_from_schedule
|
142
|
+
Resque.list_range(:scheduled, 0, -0).each do |s|
|
143
|
+
if s[params['job_name']]
|
144
|
+
Resque.redis.lrem(:scheduled, 0, s.to_json)
|
145
|
+
# Restart the scheduler on the server that has changed it's schedule
|
146
|
+
ResqueScheduler.restart(params['ip'])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
redirect_to schedule_resque_path
|
150
|
+
end
|
155
151
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
152
|
+
def start_scheduler
|
153
|
+
ResqueScheduler.start(params[:ip])
|
154
|
+
redirect_to schedule_resque_path
|
155
|
+
end
|
156
|
+
|
157
|
+
def stop_scheduler
|
158
|
+
ResqueScheduler.quit(params[:ip])
|
159
|
+
redirect_to schedule_resque_path
|
160
|
+
end
|
160
161
|
|
161
|
-
|
162
|
+
# resque-status actions
|
162
163
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
164
|
+
def statuses
|
165
|
+
@start = params[:start].to_i
|
166
|
+
@end = @start + (params[:per_page] || 20)
|
167
|
+
@statuses = Resque::Plugins::Status::Hash.statuses(@start, @end)
|
168
|
+
@size = Resque::Plugins::Status::Hash.status_ids.size
|
169
|
+
respond_to do |format|
|
170
|
+
format.js { render json: @statuses }
|
171
|
+
format.html { render :statuses }
|
172
|
+
end
|
171
173
|
end
|
172
|
-
end
|
173
174
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
175
|
+
def clear_statuses
|
176
|
+
Resque::Plugins::Status::Hash.clear
|
177
|
+
redirect_to statuses_resque_path
|
178
|
+
end
|
178
179
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
180
|
+
def status
|
181
|
+
@status = Resque::Plugins::Status::Hash.get(params[:id])
|
182
|
+
respond_to do |format|
|
183
|
+
format.js { render json: @status }
|
184
|
+
format.html { render :status }
|
185
|
+
end
|
184
186
|
end
|
185
|
-
end
|
186
187
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
188
|
+
def kill
|
189
|
+
Resque::Plugins::Status::Hash.kill(params[:id])
|
190
|
+
s = Resque::Plugins::Status::Hash.get(params[:id])
|
191
|
+
s.status = 'killed'
|
192
|
+
Resque::Plugins::Status::Hash.set(params[:id], s)
|
193
|
+
redirect_to statuses_resque_path
|
194
|
+
end
|
194
195
|
|
195
|
-
|
196
|
-
|
196
|
+
def cleaner
|
197
|
+
load_cleaner_filter
|
197
198
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
199
|
+
@jobs = @cleaner.select
|
200
|
+
@stats, @total = {}, {"total" => 0, "1h" => 0, "3h" => 0, "1d" => 0, "3d" => 0, "7d" => 0}
|
201
|
+
@jobs.each do |job|
|
202
|
+
klass = job["payload"]["class"]
|
203
|
+
failed_at = Time.parse job["failed_at"]
|
203
204
|
|
204
|
-
|
205
|
-
|
205
|
+
@stats[klass] ||= {"total" => 0, "1h" => 0, "3h" => 0, "1d" => 0, "3d" => 0, "7d" => 0}
|
206
|
+
items = [@stats[klass], @total]
|
206
207
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
208
|
+
items.each { |a| a["total"] += 1 }
|
209
|
+
items.each { |a| a["1h"] += 1 } if failed_at >= hours_ago(1)
|
210
|
+
items.each { |a| a["3h"] += 1 } if failed_at >= hours_ago(3)
|
211
|
+
items.each { |a| a["1d"] += 1 } if failed_at >= hours_ago(24)
|
212
|
+
items.each { |a| a["3d"] += 1 } if failed_at >= hours_ago(24*3)
|
213
|
+
items.each { |a| a["7d"] += 1 } if failed_at >= hours_ago(24*7)
|
214
|
+
end
|
213
215
|
end
|
214
|
-
end
|
215
216
|
|
216
|
-
|
217
|
-
|
217
|
+
def cleaner_list
|
218
|
+
load_cleaner_filter
|
218
219
|
|
219
|
-
|
220
|
+
block = filter_block
|
220
221
|
|
221
|
-
|
222
|
+
@failed = @cleaner.select(&block).reverse
|
222
223
|
|
223
|
-
|
224
|
-
|
225
|
-
|
224
|
+
url = "cleaner_list?c=#{@klass}&ex=#{@exception}&f=#{@from}&t=#{@to}"
|
225
|
+
@dump_url = "cleaner_dump?c=#{@klass}&ex=#{@exception}&f=#{@from}&t=#{@to}"
|
226
|
+
@paginate = ResqueManager::Paginate.new(@failed, url, params[:p].to_i)
|
226
227
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
228
|
+
@klasses = @cleaner.stats_by_class.keys
|
229
|
+
@exceptions = @cleaner.stats_by_exception.keys
|
230
|
+
@count = @cleaner.select(&block).size
|
231
|
+
end
|
231
232
|
|
232
|
-
|
233
|
-
|
233
|
+
def cleaner_exec
|
234
|
+
load_cleaner_filter
|
234
235
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
236
|
+
if params[:select_all_pages]!="1"
|
237
|
+
@sha1 = {}
|
238
|
+
params[:sha1].split(",").each { |s| @sha1[s] = true }
|
239
|
+
end
|
239
240
|
|
240
|
-
|
241
|
+
block = filter_block
|
241
242
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
243
|
+
@count =
|
244
|
+
case params[:form_action]
|
245
|
+
when "clear" then
|
246
|
+
@cleaner.clear(&block)
|
247
|
+
when "retry_and_clear" then
|
248
|
+
@cleaner.requeue(true, &block)
|
249
|
+
when "retry" then
|
250
|
+
@cleaner.requeue(false, {}, &block)
|
251
|
+
end
|
251
252
|
|
252
|
-
|
253
|
-
|
253
|
+
@link_url = "cleaner_list?c=#{@klass}&ex=#{@exception}&f=#{@from}&t=#{@to}"
|
254
|
+
end
|
254
255
|
|
255
|
-
|
256
|
-
|
256
|
+
def cleaner_dump
|
257
|
+
load_cleaner_filter
|
257
258
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
259
|
+
block = filter_block
|
260
|
+
failures = @cleaner.select(&block)
|
261
|
+
# pretty generate throws an error with the json gem on jruby
|
262
|
+
output = JSON.pretty_generate(failures) rescue failures.to_json
|
263
|
+
render :json => output
|
264
|
+
end
|
264
265
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
266
|
+
def cleaner_stale
|
267
|
+
@cleaner.clear_stale
|
268
|
+
redirect_to cleaner_resque_path
|
269
|
+
end
|
269
270
|
|
270
271
|
|
271
|
-
|
272
|
+
private
|
272
273
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
274
|
+
def check_connection
|
275
|
+
Resque.keys
|
276
|
+
rescue Errno::ECONNREFUSED
|
277
|
+
render(:template => 'resque_manager/resque/error', :layout => false, :locals => {:error => "Can't connect to Redis! (#{Resque.redis_id})"})
|
278
|
+
false
|
279
|
+
end
|
279
280
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
281
|
+
def find_worker(worker)
|
282
|
+
return nil if worker.blank?
|
283
|
+
worker = CGI::unescape(worker)
|
284
|
+
first_part, *rest = worker.split(':')
|
285
|
+
first_part.gsub!(/_/, '.')
|
286
|
+
Resque::Worker.find("#{first_part}:#{rest.join(':')}")
|
287
|
+
end
|
287
288
|
|
288
|
-
|
289
|
+
# resque-cleaner methods
|
289
290
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
291
|
+
def get_cleaner
|
292
|
+
@cleaner ||= Resque::Plugins::ResqueCleaner.new
|
293
|
+
@cleaner.print_message = false
|
294
|
+
@cleaner
|
295
|
+
end
|
295
296
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
297
|
+
def load_cleaner_filter
|
298
|
+
@from = params[:f].blank? ? nil : params[:f]
|
299
|
+
@to = params[:t].blank? ? nil : params[:t]
|
300
|
+
@klass = params[:c].blank? ? nil : params[:c]
|
301
|
+
@exception = params[:ex].blank? ? nil : params[:ex]
|
302
|
+
end
|
302
303
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
304
|
+
def filter_block
|
305
|
+
lambda { |j|
|
306
|
+
(!@from || j.after?(hours_ago(@from))) &&
|
307
|
+
(!@to || j.before?(hours_ago(@to))) &&
|
308
|
+
(!@klass || j.klass?(@klass)) &&
|
309
|
+
(!@exception || j.exception?(@exception)) &&
|
310
|
+
(!@sha1 || @sha1[Digest::SHA1.hexdigest(j.to_json)])
|
311
|
+
}
|
312
|
+
end
|
312
313
|
|
313
|
-
|
314
|
-
|
314
|
+
def hours_ago(h)
|
315
|
+
Time.now - h.to_i*60*60
|
316
|
+
end
|
315
317
|
end
|
316
318
|
end
|
317
319
|
end
|