dvdplm-taskr 0.3.1
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/CHANGELOG.txt +1 -0
- data/GPLv3-LICENSE.txt +674 -0
- data/History.txt +50 -0
- data/Manifest.txt +34 -0
- data/README.txt +27 -0
- data/Rakefile +85 -0
- data/bin/taskr +35 -0
- data/bin/taskr-ctl +32 -0
- data/config.example.yml +176 -0
- data/examples/active_resource_client_example.rb +46 -0
- data/examples/php_client_example.php +125 -0
- data/lib/public/prototype.js +3271 -0
- data/lib/public/taskr.css +45 -0
- data/lib/taskr.rb +97 -0
- data/lib/taskr/actions.rb +318 -0
- data/lib/taskr/controllers.rb +338 -0
- data/lib/taskr/environment.rb +48 -0
- data/lib/taskr/helpers.rb +79 -0
- data/lib/taskr/models.rb +370 -0
- data/lib/taskr/version.rb +9 -0
- data/lib/taskr/views.rb +455 -0
- data/setup.rb +1585 -0
- data/taskr4rails/LICENSE.txt +504 -0
- data/taskr4rails/README +31 -0
- data/taskr4rails/Rakefile +22 -0
- data/taskr4rails/init.rb +1 -0
- data/taskr4rails/install.rb +26 -0
- data/taskr4rails/lib/taskr4rails_controller.rb +76 -0
- data/taskr4rails/tasks/taskr4rails_tasks.rake +4 -0
- data/taskr4rails/test/taskr4rails_test.rb +8 -0
- data/taskr4rails/uninstall.rb +1 -0
- data/test.rb +3 -0
- data/test/taskr_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +140 -0
@@ -0,0 +1,338 @@
|
|
1
|
+
# This file is part of Taskr.
|
2
|
+
#
|
3
|
+
# Taskr is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# Taskr is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with Taskr. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Taskr::Controllers
|
17
|
+
|
18
|
+
class ActionTypes < REST 'action_types'
|
19
|
+
def list
|
20
|
+
@actions = Taskr::Actions.list
|
21
|
+
|
22
|
+
render :action_list
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Actions < REST 'actions'
|
27
|
+
def parameters_form(id)
|
28
|
+
@num = @input[:num] || 0
|
29
|
+
@action = Taskr::Actions.list.find {|a| a.to_s =~ Regexp.new("#{id}$")}
|
30
|
+
if @action
|
31
|
+
render :action_parameters_form
|
32
|
+
else
|
33
|
+
@status = 404
|
34
|
+
"Action #{id.inspect} not defined"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def new
|
39
|
+
@num = @input[:num] || 0
|
40
|
+
@actions = Taskr::Actions.list
|
41
|
+
render :action_form
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Tasks < REST 'tasks'
|
46
|
+
include Taskr::Models
|
47
|
+
|
48
|
+
# List of tasks.
|
49
|
+
def list
|
50
|
+
@tasks = Task.find(:all, :include => [:task_actions])
|
51
|
+
|
52
|
+
render :tasks_list
|
53
|
+
end
|
54
|
+
|
55
|
+
# Input for a new task.
|
56
|
+
def new
|
57
|
+
@actions = Taskr::Actions.list
|
58
|
+
|
59
|
+
render :new_task
|
60
|
+
end
|
61
|
+
|
62
|
+
# Retrieve details for an existing task.
|
63
|
+
def read(id)
|
64
|
+
@task = Task.find(id, :include => [:task_actions])
|
65
|
+
|
66
|
+
render :view_task
|
67
|
+
end
|
68
|
+
|
69
|
+
def edit(task_id)
|
70
|
+
@task = Task.find(task_id, :include => [:task_actions])
|
71
|
+
@actions = Taskr::Actions.list
|
72
|
+
render :edit_task
|
73
|
+
end
|
74
|
+
|
75
|
+
def update(task_id)
|
76
|
+
$LOG.debug "Update Input params: #{@input.inspect}"
|
77
|
+
@task = Task.find(task_id, :include => [:task_actions])
|
78
|
+
params = normalize_input(@input)
|
79
|
+
@task.attributes= {
|
80
|
+
:name => params[:name],
|
81
|
+
:schedule_method => params[:schedule_method],
|
82
|
+
:schedule_when => params[:schedule_when],
|
83
|
+
:memo => params[:memo]
|
84
|
+
}
|
85
|
+
|
86
|
+
@task.task_actions.each do |action|
|
87
|
+
$LOG.debug("Updating parameters for #{action.inspect}")
|
88
|
+
action_params = params[:action].delete("action_id_#{action.id}")
|
89
|
+
$LOG.debug("Using values #{action_params.inspect}")
|
90
|
+
next unless action_params
|
91
|
+
action_params.each do |param_name, value|
|
92
|
+
$LOG.debug("Looking up \"#{param_name}\". Setting value to \"#{value}\"")
|
93
|
+
action.parameters.find_by_name(param_name).update_attribute(:value, value)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create new actions/action_parameters for the remaining params
|
98
|
+
unless params[:action].empty?
|
99
|
+
params[:action].map do |num, params|
|
100
|
+
$LOG.debug "Looping remaining action_parameters: #{params.inspect} (num: #{num})"
|
101
|
+
action_class = get_action_class(params[:action_class_name])
|
102
|
+
action = TaskAction.new(:order => params[:order] || (@task.task_actions.maximum(:order)+1) || num, :action_class_name => action_class.to_s)
|
103
|
+
|
104
|
+
action_class.parameters.each do |p|
|
105
|
+
value = params[p]
|
106
|
+
value = nil if value.blank?
|
107
|
+
action.action_parameters << TaskActionParameter.new(:name => p, :value => value)
|
108
|
+
end
|
109
|
+
@task.task_actions << action
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
unless @task.valid?
|
114
|
+
@status = 500
|
115
|
+
@actions = Taskr::Actions.list
|
116
|
+
else
|
117
|
+
@task.save!
|
118
|
+
@status = 200
|
119
|
+
end
|
120
|
+
|
121
|
+
@task.reload # Ensure any updates to the record goes in
|
122
|
+
|
123
|
+
Taskr.scheduler.unschedule(@task.scheduler_job_id)
|
124
|
+
@task.schedule! Taskr.scheduler
|
125
|
+
|
126
|
+
$LOG.info "Task \"#{@task.name}\" (ID: #{@task.id}) updated sucessfully."
|
127
|
+
redirect R(@task)
|
128
|
+
end
|
129
|
+
|
130
|
+
def normalize_input(hsh)
|
131
|
+
hsh[:task] || hsh["0"] || hsh
|
132
|
+
end
|
133
|
+
|
134
|
+
def normalize_actions_params(input_params)
|
135
|
+
$LOG.debug "normalize_actions_params Normalizing: #{input_params.inspect}"
|
136
|
+
# some gymnastics here to provide compatibility for the way various
|
137
|
+
# REST client libraries submit data
|
138
|
+
actions_data = input_params[:actions] || input_params[:action]
|
139
|
+
|
140
|
+
raise ArgumentError, "Missing action(s) parameter." if actions_data.blank?
|
141
|
+
actions = case actions_data
|
142
|
+
when Array
|
143
|
+
$LOG.debug "normalize_actions_params Plain Array. Returning as-is."
|
144
|
+
actions_data
|
145
|
+
when Hash
|
146
|
+
$LOG.debug "normalize_actions_params Some weird Hash. Injecting."
|
147
|
+
actions_data.inject([]) do |acc,(i,a)|
|
148
|
+
$LOG.debug "normalize_actions_params acc: #{acc.inspect} index: #{i.inspect} array: #{a.inspect}."
|
149
|
+
acc << a
|
150
|
+
acc
|
151
|
+
end
|
152
|
+
else
|
153
|
+
$LOG.debug "normalize_actions_params Not a weird hash.\n\tactions_data[:action]: #{actions_data[:action].inspect}\n\tactions_data[:actions]: #{actions_data[:actions].inspect}\n\tactions_data: #{actions_data.inspect}\n\n"
|
154
|
+
actions_data[:action] || actions_data[:actions] || actions_data
|
155
|
+
end
|
156
|
+
actions = [actions] unless actions.kind_of? Array
|
157
|
+
$LOG.debug "normalize_actions_params DONE. Returning: #{actions.inspect}"
|
158
|
+
actions
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_action_class(class_name)
|
162
|
+
action_class_name = "Taskr::Actions::#{class_name}" unless class_name =~ /^Taskr::Actions::/
|
163
|
+
|
164
|
+
begin
|
165
|
+
action_class = action_class_name.constantize
|
166
|
+
unless action_class.include? Rufus::Schedulable
|
167
|
+
raise ArgumentError,
|
168
|
+
"#{a[:action_class_name].inspect} cannot be used as an action because it does not include the Rufus::Schedulable module."
|
169
|
+
end
|
170
|
+
rescue NameError
|
171
|
+
raise ArgumentError,
|
172
|
+
"#{a[:action_class_name].inspect} is not defined (i.e. there is no such action class)."
|
173
|
+
end
|
174
|
+
action_class
|
175
|
+
end
|
176
|
+
|
177
|
+
# Create and schedule a new task.
|
178
|
+
def create
|
179
|
+
puts @input.inspect
|
180
|
+
begin
|
181
|
+
# the "0" is for compatibility with PHP's Zend_Rest_Client
|
182
|
+
task_data = @input[:task] || @input["0"] || @input
|
183
|
+
|
184
|
+
name = task_data[:name]
|
185
|
+
created_by = @env['REMOTE_HOST']
|
186
|
+
schedule_method = task_data[:schedule_method]
|
187
|
+
schedule_when = task_data[:schedule_when]
|
188
|
+
memo = task_data[:memo]
|
189
|
+
|
190
|
+
@task = Task.new(
|
191
|
+
:name => name,
|
192
|
+
:created_by => created_by,
|
193
|
+
:schedule_method => schedule_method,
|
194
|
+
:schedule_when => schedule_when,
|
195
|
+
:memo => memo
|
196
|
+
)
|
197
|
+
|
198
|
+
# some gymnastics here to provide compatibility for the way various
|
199
|
+
# REST client libraries submit data
|
200
|
+
actions_data = task_data[:actions] || task_data[:action]
|
201
|
+
|
202
|
+
raise ArgumentError, "Missing action(s) parameter." if actions_data.blank?
|
203
|
+
|
204
|
+
if actions_data.kind_of?(Array)
|
205
|
+
actions = actions_data
|
206
|
+
elsif actions_data["0"]
|
207
|
+
actions = []
|
208
|
+
actions_data.each do |i,a|
|
209
|
+
actions << a
|
210
|
+
end
|
211
|
+
else
|
212
|
+
actions = actions_data[:action] || actions_data[:actions] || actions_data
|
213
|
+
end
|
214
|
+
|
215
|
+
actions = [actions] unless actions.kind_of? Array
|
216
|
+
#puts actions.inspect
|
217
|
+
|
218
|
+
i = 0
|
219
|
+
actions.each do |a|
|
220
|
+
#puts a.inspect
|
221
|
+
action_class_name = a[:action_class_name]
|
222
|
+
action_class_name = "Taskr::Actions::#{action_class_name}" unless action_class_name =~ /^Taskr::Actions::/
|
223
|
+
|
224
|
+
begin
|
225
|
+
action_class = action_class_name.constantize
|
226
|
+
unless action_class.include? Rufus::Schedulable
|
227
|
+
raise ArgumentError,
|
228
|
+
"#{a[:action_class_name].inspect} cannot be used as an action because it does not include the Rufus::Schedulable module."
|
229
|
+
end
|
230
|
+
rescue NameError
|
231
|
+
raise ArgumentError,
|
232
|
+
"#{a[:action_class_name].inspect} is not defined (i.e. there is no such action class)."
|
233
|
+
end
|
234
|
+
|
235
|
+
action = TaskAction.new(:order => a[:order] || i, :action_class_name => action_class_name)
|
236
|
+
|
237
|
+
|
238
|
+
action_class.parameters.each do |p|
|
239
|
+
value = a[p]
|
240
|
+
value = nil if value.blank?
|
241
|
+
action.action_parameters << TaskActionParameter.new(:name => p, :value => value)
|
242
|
+
end
|
243
|
+
|
244
|
+
@task.task_actions << action
|
245
|
+
i += 1
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
unless @task.valid?
|
250
|
+
@status = 500
|
251
|
+
@actions = Taskr::Actions.list
|
252
|
+
return render(:new_task)
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
@task.schedule! Taskr.scheduler
|
257
|
+
|
258
|
+
if @task.save
|
259
|
+
location = "/tasks/#{@task.id}?format=#{@format}"
|
260
|
+
$LOG.debug "#{@task} saved successfuly. Setting Location header to #{location.inspect}."
|
261
|
+
@headers['Location'] = location
|
262
|
+
end
|
263
|
+
|
264
|
+
return render(:view_task)
|
265
|
+
rescue => e
|
266
|
+
puts e.inspect
|
267
|
+
puts e.backtrace
|
268
|
+
raise e
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def run(id)
|
273
|
+
@task = Task.find(id, :include => [:task_actions])
|
274
|
+
|
275
|
+
action = @task.prepare_action
|
276
|
+
|
277
|
+
LogEntry.info(@task, "Manually executing task #{@task}.")
|
278
|
+
|
279
|
+
begin
|
280
|
+
action.trigger
|
281
|
+
rescue
|
282
|
+
# ok to catch exception silently. it should have gotten logged by the action
|
283
|
+
end
|
284
|
+
|
285
|
+
redirect R(@task)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Unschedule and delete an existing task.
|
289
|
+
def destroy(id)
|
290
|
+
@task = Task.find(id)
|
291
|
+
if @task.scheduler_job_id
|
292
|
+
$LOG.debug "Unscheduling task #{@task}..."
|
293
|
+
Taskr.scheduler.unschedule(@task.scheduler_job_id)
|
294
|
+
end
|
295
|
+
@task.destroy
|
296
|
+
|
297
|
+
if @task.frozen?
|
298
|
+
@status = 200
|
299
|
+
if @format == :XML
|
300
|
+
""
|
301
|
+
else
|
302
|
+
return redirect(R(Tasks, :list))
|
303
|
+
end
|
304
|
+
else
|
305
|
+
_error("Task #{id} was not destroyed.", 500)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# Reload a task
|
310
|
+
def reload(id)
|
311
|
+
@task = Task.find(id)
|
312
|
+
$LOG.debug "Re-scheduling task #{@task}..."
|
313
|
+
if @task.scheduler_job_id
|
314
|
+
Taskr.scheduler.unschedule(@task.scheduler_job_id)
|
315
|
+
$LOG.debug "\t...unscheduled task #{@task}..."
|
316
|
+
end
|
317
|
+
@task.schedule! Taskr.scheduler
|
318
|
+
$LOG.debug "\t...scheduled task #{@task}...\n"
|
319
|
+
redirect R(@task)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class LogEntries < REST 'log_entries'
|
324
|
+
def list
|
325
|
+
@since = @input[:since]
|
326
|
+
|
327
|
+
@level = ['DEBUG', 'INFO', 'WARN', 'ERROR']
|
328
|
+
@level.index(@input[:level]).times {@level.shift} if @input[:level]
|
329
|
+
|
330
|
+
@log_entries = LogEntry.find(:all,
|
331
|
+
:conditions => ['task_id = ? AND IF(?,timestamp > ?,1) AND level IN (?)',
|
332
|
+
@input[:task_id], !@since.blank?, @since, @level],
|
333
|
+
:order => 'timestamp DESC, id DESC')
|
334
|
+
|
335
|
+
render :log_entries_list
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# This file is part of Taskr.
|
2
|
+
#
|
3
|
+
# Taskr is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# Taskr is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with Taskr. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
$: << File.dirname(File.expand_path(__FILE__))
|
17
|
+
|
18
|
+
# Try to load local versions of Picnic and Reststop if possible...
|
19
|
+
$: << File.dirname(File.expand_path(__FILE__))+"/../../../picnic/lib"
|
20
|
+
$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/picnic/lib"
|
21
|
+
$: << File.dirname(File.expand_path(__FILE__))+"/../../../reststop/lib"
|
22
|
+
$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/reststop/lib"
|
23
|
+
|
24
|
+
# active_resource needs newer versions of active_support, but this conflicts
|
25
|
+
# with active_record, so we need a newer version of that as well (yes, it's a mess)
|
26
|
+
#$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/activeresource/lib"
|
27
|
+
#$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/activesupport/lib"
|
28
|
+
#$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/activerecord/lib"
|
29
|
+
|
30
|
+
require 'rubygems'
|
31
|
+
|
32
|
+
require 'active_support'
|
33
|
+
#require 'active_resource'
|
34
|
+
require 'active_record'
|
35
|
+
|
36
|
+
|
37
|
+
# make things backwards-compatible for rubygems < 0.9.0
|
38
|
+
unless Object.method_defined? :gem
|
39
|
+
alias gem require_gem
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'picnic'
|
43
|
+
require 'camping/db'
|
44
|
+
|
45
|
+
require 'reststop'
|
46
|
+
|
47
|
+
gem 'rufus-scheduler', '~> 1.0.7'
|
48
|
+
require 'rufus/scheduler'
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# This file is part of Taskr.
|
2
|
+
#
|
3
|
+
# Taskr is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# Taskr is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with Taskr. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Taskr::Helpers
|
17
|
+
def taskr_response_xml(result, &block)
|
18
|
+
instruct!
|
19
|
+
tag!("response", 'result' => result, 'xmlns:taskr' => "http://taskr.googlecode.com") do
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def html_task_action_li(ta)
|
25
|
+
li ta.action_class_name
|
26
|
+
ul do
|
27
|
+
ta.action_parameters.each do |ap|
|
28
|
+
li do
|
29
|
+
label "#{ap.name}:"
|
30
|
+
pre ap.value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def html_scaffold
|
37
|
+
html do
|
38
|
+
head do
|
39
|
+
title "Taskr"
|
40
|
+
link(:rel => 'stylesheet', :type => 'text/css', :href => '/public/taskr.css')
|
41
|
+
script(:type => 'text/javascript', :src => '/public/prototype.js')
|
42
|
+
end
|
43
|
+
body do
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Taken from Rails
|
50
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
51
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
52
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
53
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
54
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
55
|
+
|
56
|
+
case distance_in_minutes
|
57
|
+
when 0..1
|
58
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
59
|
+
case distance_in_seconds
|
60
|
+
when 0..4 then 'less than 5 seconds'
|
61
|
+
when 5..9 then 'less than 10 seconds'
|
62
|
+
when 10..19 then 'less than 20 seconds'
|
63
|
+
when 20..39 then 'half a minute'
|
64
|
+
when 40..59 then 'less than a minute'
|
65
|
+
else '1 minute'
|
66
|
+
end
|
67
|
+
|
68
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
69
|
+
when 45..89 then 'about 1 hour'
|
70
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
71
|
+
when 1440..2879 then '1 day'
|
72
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
73
|
+
when 43200..86399 then 'about 1 month'
|
74
|
+
when 86400..525959 then "#{(distance_in_minutes / 43200).round} months"
|
75
|
+
when 525960..1051919 then 'about 1 year'
|
76
|
+
else "over #{(distance_in_minutes / 525960).round} years"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|