mobilize-base 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +5 -0
  2. data/LICENSE.txt +202 -20
  3. data/README.md +219 -138
  4. data/Rakefile +1 -2
  5. data/lib/mobilize-base/extensions/google_drive/acl.rb +25 -0
  6. data/lib/mobilize-base/extensions/google_drive/client_login_fetcher.rb +49 -0
  7. data/lib/mobilize-base/extensions/google_drive/file.rb +80 -0
  8. data/lib/mobilize-base/extensions/{google_drive.rb → google_drive/worksheet.rb} +46 -173
  9. data/lib/mobilize-base/extensions/resque.rb +18 -24
  10. data/lib/mobilize-base/extensions/string.rb +12 -0
  11. data/lib/mobilize-base/handlers/gbook.rb +14 -47
  12. data/lib/mobilize-base/handlers/gdrive.rb +17 -18
  13. data/lib/mobilize-base/handlers/gfile.rb +18 -39
  14. data/lib/mobilize-base/handlers/gridfs.rb +43 -0
  15. data/lib/mobilize-base/handlers/gsheet.rb +48 -99
  16. data/lib/mobilize-base/jobtracker.rb +29 -15
  17. data/lib/mobilize-base/models/dataset.rb +33 -35
  18. data/lib/mobilize-base/models/job.rb +21 -168
  19. data/lib/mobilize-base/models/runner.rb +178 -0
  20. data/lib/mobilize-base/models/task.rb +137 -0
  21. data/lib/mobilize-base/models/user.rb +47 -0
  22. data/lib/mobilize-base/rakes.rb +59 -0
  23. data/lib/mobilize-base/version.rb +1 -1
  24. data/lib/mobilize-base.rb +20 -9
  25. data/lib/samples/gdrive.yml +12 -12
  26. data/lib/samples/gridfs.yml +9 -0
  27. data/lib/samples/gsheet.yml +6 -0
  28. data/lib/samples/jobtracker.yml +9 -9
  29. data/lib/samples/mongoid.yml +3 -3
  30. data/mobilize-base.gemspec +1 -1
  31. data/test/base1_task1.yml +3 -0
  32. data/test/base_job_rows.yml +13 -0
  33. data/test/mobilize-base_test.rb +59 -0
  34. metadata +20 -9
  35. data/lib/mobilize-base/handlers/mongodb.rb +0 -32
  36. data/lib/mobilize-base/models/requestor.rb +0 -232
  37. data/lib/mobilize-base/tasks.rb +0 -43
  38. data/test/mobilize_test.rb +0 -108
@@ -1,7 +1,7 @@
1
1
  module Mobilize
2
2
  module Jobtracker
3
3
  def Jobtracker.config
4
- Base.config('jobtracker')[Base.env]
4
+ Base.config('jobtracker')
5
5
  end
6
6
 
7
7
  #modify this to increase the frequency of request cycles
@@ -14,8 +14,8 @@ module Mobilize
14
14
  Jobtracker.config['notification_freq']
15
15
  end
16
16
 
17
- def Jobtracker.requestor_refresh_freq
18
- Jobtracker.config['requestor_refresh_freq']
17
+ def Jobtracker.runner_read_freq
18
+ Jobtracker.config['runner_read_freq']
19
19
  end
20
20
 
21
21
  #long running tolerance
@@ -28,11 +28,11 @@ module Mobilize
28
28
  end
29
29
 
30
30
  def Jobtracker.admin_emails
31
- Jobtracker.admins.map{|a| a['email']}
31
+ Jobtracker.admins.map{|a| a['email'] }
32
32
  end
33
33
 
34
34
  def Jobtracker.worker
35
- Resque.find_worker_by_mongo_id("jobtracker")
35
+ Resque.find_worker_by_path("jobtracker")
36
36
  end
37
37
 
38
38
  def Jobtracker.workers(state="all")
@@ -49,7 +49,7 @@ module Mobilize
49
49
 
50
50
  def Jobtracker.update_status(msg)
51
51
  #Jobtracker has no persistent database state
52
- Resque.update_job_status("jobtracker",msg)
52
+ Resque.set_worker_args_by_path("jobtracker",{'status'=>msg})
53
53
  return true
54
54
  end
55
55
 
@@ -96,7 +96,7 @@ module Mobilize
96
96
  end
97
97
 
98
98
  def Jobtracker.enqueue!
99
- ::Resque::Job.create(Resque.queue_name, Jobtracker, 'jobtracker',{'status'=>'working'})
99
+ ::Resque::Job.create(Resque.queue_name, Jobtracker, 'jobtracker',{})
100
100
  end
101
101
 
102
102
  def Jobtracker.restart!
@@ -107,8 +107,9 @@ module Mobilize
107
107
 
108
108
  def Jobtracker.restart_workers!
109
109
  Jobtracker.kill_workers
110
- sleep 5
110
+ sleep 10
111
111
  Jobtracker.prep_workers
112
+ Jobtracker.update_status("put workers back on the queue")
112
113
  end
113
114
 
114
115
  def Jobtracker.stop!
@@ -117,7 +118,7 @@ module Mobilize
117
118
  sleep 5
118
119
  i=0
119
120
  while Jobtracker.status=='stopping'
120
- puts "#{Jobtracker.to_s} still on queue, waiting"
121
+ Jobtracker.update_status("#{Jobtracker.to_s} still on queue, waiting")
121
122
  sleep 5
122
123
  i+=1
123
124
  end
@@ -175,7 +176,7 @@ module Mobilize
175
176
  notifs.each do |notif|
176
177
  Email.write(n['subj'],notif['body']).deliver
177
178
  Jobtracker.last_notification=Time.now.utc.to_s
178
- "Sent notification at #{Jobtracker.last_notification}".oputs
179
+ Jobtracker.update_status("Sent notification at #{Jobtracker.last_notification}")
179
180
  end
180
181
  end
181
182
  return true
@@ -183,13 +184,14 @@ module Mobilize
183
184
 
184
185
  def Jobtracker.perform(id,*args)
185
186
  while Jobtracker.status != 'stopping'
186
- requestors = Requestor.all
187
+ users = User.all
187
188
  Jobtracker.run_notifications
188
- requestors.each do |r|
189
- Jobtracker.update_status("Checking requestor #{r.name}")
189
+ users.each do |u|
190
+ r = u.runner
191
+ Jobtracker.update_status("Checking #{r.path}")
190
192
  if r.is_due?
191
193
  r.enqueue!
192
- Jobtracker.update_status("Enqueued requestor #{r.name}")
194
+ Jobtracker.update_status("Enqueued #{r.path}")
193
195
  end
194
196
  end
195
197
  sleep 5
@@ -221,7 +223,7 @@ module Mobilize
221
223
  def Jobtracker.set_test_env
222
224
  ENV['MOBILIZE_ENV']='test'
223
225
  ::Resque.redis="localhost:9736"
224
- mongoid_config_path = "#{Base.root}/config/mongoid.yml"
226
+ mongoid_config_path = "#{Base.root}/config/mobilize/mongoid.yml"
225
227
  Mongoid.load!(mongoid_config_path, Base.env)
226
228
  end
227
229
 
@@ -233,5 +235,17 @@ module Mobilize
233
235
  end
234
236
  end
235
237
  end
238
+
239
+ def Jobtracker.build_test_runner(user_name)
240
+ Jobtracker.set_test_env
241
+ u = User.where(:name=>user_name).first
242
+ Jobtracker.update_status("delete old books and datasets")
243
+ # delete any old runner from previous test runs
244
+ gdrive_slot = Gdrive.owner_email
245
+ u.runner.gsheet(gdrive_slot).spreadsheet.delete
246
+ Jobtracker.update_status("enqueue jobtracker, wait 45s")
247
+ Mobilize::Jobtracker.start
248
+ sleep 45
249
+ end
236
250
  end
237
251
  end
@@ -2,69 +2,67 @@ module Mobilize
2
2
  class Dataset
3
3
  include Mongoid::Document
4
4
  include Mongoid::Timestamps
5
- field :requestor_id, type: String
6
5
  field :handler, type: String
7
- field :name, type: String
6
+ field :path, type: String
8
7
  field :url, type: String
9
- field :size, type: Fixnum
8
+ field :raw_size, type: Fixnum
10
9
  field :last_cached_at, type: Time
10
+ field :last_cache_handler, type: String
11
11
  field :last_read_at, type: Time
12
12
  field :cache_expire_at, type: Time
13
13
 
14
- index({ requestor_id: 1})
15
- index({ handler: 1})
16
- index({ name: 1})
17
-
18
- before_destroy :destroy_cache
14
+ index({ handler: 1, path: 1}, { unique: true})
19
15
 
20
16
  def read
21
17
  dst = self
22
- if dst.last_cached_at and (dst.cache_expire_at.nil? or dst.cache_expire_at > Time.now.utc)
23
- return dst.read_cache
24
- else
25
- return "Mobilize::#{dst.handler.humanize}".constantize.read_by_dst_id(dst.id.to_s)
26
- end
18
+ return "Mobilize::#{dst.handler.humanize}".constantize.read_by_path(dst.path)
27
19
  end
28
20
 
29
- def Dataset.find_by_handler_and_name(handler,name)
30
- Dataset.where(handler: handler, name: name).first
31
- end
32
-
33
- def Dataset.find_or_create_by_handler_and_name(handler,name)
34
- dst = Dataset.where(handler: handler, name: name).first
35
- dst = Dataset.create(handler: handler, name: name) unless dst
36
- return dst
21
+ def Dataset.find_by_handler_and_path(handler,path)
22
+ Dataset.where(handler: handler, path: path).first
37
23
  end
38
24
 
39
- def Dataset.find_or_create_by_requestor_id_and_handler_and_name(requestor_id,handler,name)
40
- dst = Dataset.where(requestor_id: requestor_id, handler: handler, name: name).first
41
- dst = Dataset.create(requestor_id: requestor_id, handler: handler, name: name) unless dst
25
+ def Dataset.find_or_create_by_handler_and_path(handler,path)
26
+ dst = Dataset.where(handler: handler, path: path).first
27
+ dst = Dataset.create(handler: handler, path: path) unless dst
42
28
  return dst
43
29
  end
44
30
 
45
- def write(data)
31
+ def write(string)
46
32
  dst = self
47
- dst.handler.humanize.constantize.write_by_dst_id(dst.id.to_s,data)
33
+ "Mobilize::#{dst.handler.humanize}".constantize.write_by_path(dst.path,string)
34
+ dst.raw_size = string.length
48
35
  dst.save!
49
36
  return true
50
37
  end
51
38
 
52
- def read_cache
39
+ def cache_valid?
53
40
  dst = self
54
- dst.update_attributes(:last_read_at=>Time.now.utc)
55
- return Mongodb.read_by_filename(dst.id.to_s)
41
+ return true if dst.last_cached_at and (dst.cache_expire_at.nil? or dst.cache_expire_at > Time.now.utc)
56
42
  end
57
43
 
58
- def write_cache(string,expire_at=nil)
44
+ def read_cache(cache_handler="gridfs")
59
45
  dst = self
60
- Mongodb.write_by_filename(dst.id.to_s,string)
61
- dst.update_attributes(:last_cached_at=>Time.now.utc,:cache_expire_at=>expire_at,:size=>string.length)
62
- return true
46
+ if cache_valid?
47
+ dst.update_attributes(:last_read_at=>Time.now.utc)
48
+ return "Mobilize::#{cache_handler.humanize}".constantize.read([dst.handler,dst.path].join("://"))
49
+ else
50
+ raise "Cache invalid or not found for #{cache_handler}://#{dst.path}"
51
+ end
63
52
  end
64
53
 
65
- def delete_cache
66
- return Mongodb.delete_by_filename(dst.id.to_s)
54
+ def write_cache(string,expire_at=nil,cache_handler="gridfs")
55
+ dst = self
56
+ "Mobilize::#{cache_handler.humanize}".constantize.write([dst.handler,dst.path].join("://"),string)
57
+ dst.update_attributes(:last_cached_at=>Time.now.utc,
58
+ :last_cache_handler=>cache_handler.to_s.downcase,
59
+ :cache_expire_at=>expire_at,
60
+ :size=>string.length)
61
+ return true
67
62
  end
68
63
 
64
+ def delete_cache(cache_handler="gridfs")
65
+ return "Mobilize::#{cache_handler.humanize}".constantize.delete(dst.handler, dst.path)
66
+ end
69
67
  end
70
68
  end
@@ -2,199 +2,52 @@ module Mobilize
2
2
  class Job
3
3
  include Mongoid::Document
4
4
  include Mongoid::Timestamps
5
- field :requestor_id, type: String
6
- field :name, type: String
7
- field :active, type: Boolean #active, inactive
8
- field :schedule, type: String
9
- field :active_task, type: String
10
- field :tasks, type: String
5
+ field :path, type: String
6
+ field :active, type: Boolean
7
+ field :trigger, type: String
11
8
  field :status, type: String
12
- field :last_error, type: String
13
- field :last_trace, type: String
14
9
  field :last_completed_at, type: Time
15
- field :datasets, type: String #name of data sources
16
- field :params, type: String #JSON
17
- field :destination, type: String #output destination - could be file, could be sheet
18
10
 
19
- index({ requestor_id: 1})
20
- index({ name: 1})
11
+ index({ path: 1})
21
12
 
22
- before_destroy :destroy_output_dst_ids
23
-
24
- def worker
25
- j = self
26
- Mobilize::Resque.find_worker_by_mongo_id(j.id.to_s)
27
- end
28
-
29
- def dataset_array
13
+ def name
30
14
  j = self
31
- r = j.requestor
32
- dsts = j.datasets.split(",").map{|dst| dst.strip}
33
- dsts.map do |ps|
34
- #prepend jobspec title if there is no path separator
35
- full_ps = ps.index("/") ? ps : [r.jobspec_title,ps].join("/")
36
- #find or create dataset for this sheet
37
- dst = Dataset.find_or_create_by_handler_and_name("gsheet",full_ps)
38
- dst.update_attributes(:requestor_id=>r.id.to_s) unless dst.requestor_id
39
- dst
40
- end
41
- end
42
-
43
- def task_array
44
- self.tasks.split(",").map{|t| t.strip}
45
- end
46
-
47
- def task_output_dsts
48
- j = self
49
- r = j.requestor
50
- dst_names = j.task_array.map{|tname| [r.name,j.name,tname].join("/")}
51
- dst_names.map do |dst_name|
52
- Dataset.find_or_create_by_requestor_id_and_handler_and_name(r.id.to_s,'mongodb',dst_name)
53
- end
15
+ j.path.split("/").last
54
16
  end
55
17
 
56
- def task_idx
18
+ def tasks
57
19
  j = self
58
- j.task_array.index(j.active_task)
20
+ Task.where(:path=>/^#{j.path.escape_regex}/).to_a.sort_by{|t| t.path}
59
21
  end
60
22
 
61
- def Job.find_by_name(name)
62
- Job.where(:name=>name).first
63
- end
64
-
65
- def Job.find_all_by_requestor_id(requestor_id)
66
- Job.where(:requestor_id=>requestor_id).to_a
67
- end
68
-
69
- def Job.find_or_create_by_requestor_id_and_name(requestor_id,name)
70
- j = Job.where(:requestor_id=>requestor_id, :name=>name).first
71
- j = Job.create(:requestor_id=>requestor_id, :name=>name) unless j
23
+ def Job.find_or_create_by_path(path)
24
+ j = Job.where(:path=>path).first
25
+ j = Job.create(:path=>path) unless j
72
26
  return j
73
27
  end
74
28
 
75
- #called by Resque
76
- def Job.perform(id,*args)
77
- j = Job.find(id)
78
- r = j.requestor
79
- handler,method_name = j.active_task.split(".")
80
- begin
81
- j.update_status(%{Starting #{j.active_task} task at #{Time.now.utc}})
82
- task_output = "Mobilize::#{handler.humanize}".constantize.send("#{method_name}_by_job_id",id)
83
- #this allows user to return false if the stage didn't go as expected and needs to retry
84
- #e.g. tried to write to Google but all the accounts were in use
85
- return false if task_output == false
86
- task_output_dst = j.task_output_dsts[j.task_idx]
87
- task_output = task_output.to_s unless task_output.class == String
88
- task_output_dst.write_cache(task_output)
89
- if j.active_task == j.task_array.last
90
- j.active_task = nil
91
- j.last_error = ""
92
- j.last_trace = ""
93
- j.last_completed_at = Time.now.utc
94
- j.status = %{Completed all tasks at #{Time.now.utc}}
95
- j.save!
96
- #check for any dependent jobs, if there are, enqueue them
97
- r = j.requestor
98
- dep_jobs = Job.where(:active=>true, :requestor_id=>r.id.to_s, :schedule=>"after #{j.name}").to_a
99
- dep_jobs += Job.where(:active=>true, :schedule=>"after #{r.name}/#{j.name}").to_a
100
- #put begin/rescue so all dependencies run
101
- dep_jobs.each{|dj| begin;dj.enqueue! unless dj.is_working?;rescue;end}
102
- else
103
- #set next task
104
- j.active_task = j.task_array[j.task_idx+1]
105
- j.save!
106
- #queue up next task
107
- j.enqueue!
108
- end
109
- rescue ScriptError,StandardError => exc
110
- #record the failure in Job so it appears on spec sheets
111
- j.status='failed'
112
- j.save!
113
- j.update_status("Failed at #{Time.now.utc.to_s}")
114
- j.update_attributes(:last_error=>exc.to_s,:last_trace=>exc.backtrace.to_s)
115
- [exc.to_s,exc.backtrace.to_s].join("=>").oputs
116
- #raising here will cause the failure to show on the Resque UI
117
- raise exc
118
- end
119
- return true
120
- end
121
-
122
- def enqueue!
123
- j = self
124
- r = j.requestor
125
- j.update_attributes(:active_task=>j.task_array.first) if j.active_task.blank?
126
- ::Resque::Job.create("mobilize",Job,j.id.to_s,%{#{r.name}=>#{j.name}})
127
- return true
128
- end
129
-
130
29
  #convenience methods
131
- def requestor
132
- j = self
133
- return Requestor.find(j.requestor_id)
134
- end
135
-
136
- def restart
137
- j = self
138
- j.update_attributes(:last_completed_at=>nil)
139
- return true
140
- end
141
-
142
- def prior_task
143
- j = self
144
- return nil if j.active_task.nil?
145
- task_idx = j.task_array.index(j.active_task)
146
- return nil if task_idx==0
147
- return j.task_array[task_idx-1]
148
- end
149
-
150
- def destination_url
151
- j = self
152
- return nil if j.destination.nil?
153
- destination = j.destination
154
- dst = if j.task_array.last == 'gsheet.write'
155
- destination = [j.requestor.jobspec_title,j.destination].join("/") if destination.split("/").length==1
156
- Dataset.find_by_handler_and_name('gsheet',destination)
157
- elsif j.task_array.last == 'gfile.write'
158
- #all gfiles must end in gz
159
- destination += ".gz" unless destination.ends_with?(".gz")
160
- destination = [s.requestor.name,"_"].join + destination unless destination.starts_with?([s.requestor.name,"_"].join)
161
- Dataset.find_by_handler_and_name('gfile',destination)
162
- end
163
- return dst.url if dst
164
- end
165
-
166
- def worker_args
167
- j = self
168
- Jobtracker.get_worker_args(j.worker)
169
- end
170
-
171
- def set_worker_args(args)
172
- j = self
173
- Jobtracker.set_worker_args(j.worker,args)
174
- end
175
-
176
- def update_status(msg)
30
+ def runner
177
31
  j = self
178
- j.update_attributes(:status=>msg)
179
- Mobilize::Resque.update_job_status(j.id.to_s,msg)
180
- return true
32
+ runner_path = j.path.split("/")[0..-2].join("/")
33
+ return Runner.where(:path=>runner_path).first
181
34
  end
182
35
 
183
36
  def is_working?
184
37
  j = self
185
- Mobilize::Resque.active_mongo_ids.include?(j.id.to_s)
38
+ j.tasks.select{|t| t.is_working?}.compact.length>0
186
39
  end
187
40
 
188
41
  def is_due?
189
42
  j = self
190
- return false if j.is_working? or j.active == false or j.schedule.to_s.starts_with?("after")
43
+ return false if j.is_working? or j.active == false or j.trigger.to_s.starts_with?("after")
191
44
  last_run = j.last_completed_at
192
- #check schedule
193
- schedule = j.schedule
194
- return true if schedule == 'once'
45
+ #check trigger
46
+ trigger = j.trigger
47
+ return true if trigger == 'once'
195
48
  #strip the "every" from the front if present
196
- schedule = schedule.gsub("every","").gsub("."," ").strip
197
- value,unit,operator,job_utctime = schedule.split(" ")
49
+ trigger = trigger.gsub("every","").gsub("."," ").strip
50
+ value,unit,operator,job_utctime = trigger.split(" ")
198
51
  curr_utctime = Time.now.utc
199
52
  curr_utcdate = curr_utctime.to_date.strftime("%Y-%m-%d")
200
53
  if job_utctime
@@ -0,0 +1,178 @@
1
+ module Mobilize
2
+ class Runner
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ field :path, type: String
6
+ field :active, type: Boolean
7
+ field :status, type: String
8
+ field :last_run, type: Time
9
+
10
+ index({ path: 1})
11
+
12
+ def headers
13
+ %w{name active trigger status task1 task2 task3 task4 task5}
14
+ end
15
+
16
+ def last_cached_at
17
+ r = self
18
+ Dataset.find_or_create_by_path(r.path).last_cached_at
19
+ end
20
+
21
+ def worker
22
+ r = self
23
+ Mobilize::Resque.find_worker_by_path(r.path)
24
+ end
25
+
26
+ def Runner.find_by_email(email)
27
+ Runner.where(:email=>email).first
28
+ end
29
+
30
+ def Runner.find_by_path(path)
31
+ Runner.where(:path=>path).first
32
+ end
33
+
34
+ def Runner.perform(id,*args)
35
+ r = Runner.find_by_path(id)
36
+ #get gdrive slot for read
37
+ gdrive_slot = Gdrive.slot_worker_by_path(r.path)
38
+ unless gdrive_slot
39
+ r.update_status("no gdrive slot available")
40
+ return false
41
+ end
42
+ #make sure any updates to activity are processed first
43
+ #as in when someone runs a "once" job that has completed
44
+ r.update_gsheet(gdrive_slot)
45
+ #read the jobs in the gsheet and update models with news
46
+ r.read_gsheet(gdrive_slot)
47
+ #queue up the jobs that are due and active
48
+ r.jobs.each do |j|
49
+ begin
50
+ if j.is_due?
51
+ j.tasks.first.enqueue!
52
+ end
53
+ rescue ScriptError, StandardError => exc
54
+ j.update_status("Failed to enqueue with #{exc.to_s}")
55
+ j.update_attributes(:active=>false)
56
+ end
57
+ end
58
+ r.update_gsheet(gdrive_slot)
59
+ r.update_attributes(:last_run=>Time.now.utc)
60
+ end
61
+
62
+ def dataset
63
+ r = self
64
+ Dataset.find_or_create_by_handler_and_path("gsheet",r.path)
65
+ end
66
+
67
+ def Runner.find_or_create_by_path(path)
68
+ Runner.where(:path=>path).first || Runner.create(:path=>path,:active=>true)
69
+ end
70
+
71
+ def read_cache
72
+ r = self
73
+ r.dataset.read_cache
74
+ end
75
+
76
+ def gsheet(gdrive_slot)
77
+ r = self
78
+ jobs_sheet = Gsheet.find_or_create_by_path(r.path,gdrive_slot)
79
+ jobs_sheet.add_headers(r.headers)
80
+ jobs_sheet.delete_sheet1
81
+ return jobs_sheet
82
+ end
83
+
84
+ def read_gsheet(gdrive_slot)
85
+ r = self
86
+ gsheet_tsv = r.gsheet(gdrive_slot).to_tsv
87
+ #cache in DB
88
+ r.dataset.write_cache(gsheet_tsv)
89
+ #turn it into a hash array
90
+ gsheet_jobs = gsheet_tsv.tsv_to_hash_array
91
+ #go through each job, update relevant job with its params
92
+ done_jobs = []
93
+ #parse out the jobs and update the Job collection
94
+ gsheet_jobs.each_with_index do |rj,rj_i|
95
+ #skip non-jobs or jobs without required values
96
+ next if (rj['name'].to_s.first == "#" or ['name','active','trigger','task1'].select{|c| rj[c].to_s.strip==""}.length>0)
97
+ j = Job.find_or_create_by_path("#{r.path}/#{rj['name']}")
98
+ #update top line params
99
+ j.update_attributes(:active => rj['active'],
100
+ :trigger => rj['trigger'])
101
+ (1..5).to_a.each do |t_idx|
102
+ task_string = rj["task#{t_idx.to_s}"]
103
+ break if task_string.to_s.length==0
104
+ t = Task.find_or_create_by_path("#{j.path}/task#{t_idx.to_s}")
105
+ #parse command string, update task with it
106
+ t_handler, call, param_string = [""*3]
107
+ task_string.split(" ").ie do |spls|
108
+ t_handler = spls.first.split(".").first
109
+ call = spls.first.split(".").last
110
+ param_string = spls[1..-1].join(" ").strip
111
+ end
112
+ t.update_attributes(:call=>call, :handler=>t_handler, :param_string=>param_string)
113
+ end
114
+ r.update_status("Updated #{j.path} tasks at #{Time.now.utc}")
115
+ #add this job to list of read ones
116
+ done_jobs << j
117
+ end
118
+ #delete user jobs that are not included in Runner
119
+ (r.jobs.map{|j| j.path} - done_jobs.map{|j| j.path}).each do |rj_path|
120
+ j = Job.where(:path=>rj_path).first
121
+ j.delete if j
122
+ r.update_status("Deleted job:#{rj_path}")
123
+ end
124
+ r.update_status("jobs read at #{Time.now.utc}")
125
+ return true
126
+ end
127
+
128
+ def update_gsheet(gdrive_slot)
129
+ r = self
130
+ jobs_gsheet = r.gsheet(gdrive_slot)
131
+ upd_rows = r.jobs.map{|j| {'name'=>j.name, 'active'=>j.active, 'status'=>j.status} }
132
+ jobs_gsheet.add_or_update_rows(upd_rows)
133
+ r.update_status("gsheet updated")
134
+ return true
135
+ end
136
+
137
+ def jobs(jname=nil)
138
+ r = self
139
+ js = Job.where(:path=>/^#{r.path.escape_regex}/).to_a
140
+ if jname
141
+ return js.sel{|j| j.name == jname}.first
142
+ else
143
+ return js
144
+ end
145
+ end
146
+
147
+ def user
148
+ r = self
149
+ user_name = r.path.split("_").second.split("(").first.split("/").first
150
+ User.where(:email=>[user_name,Gdrive.domain].join("@")).first
151
+ end
152
+
153
+ def update_status(msg)
154
+ r = self
155
+ r.update_attributes(:status=>msg)
156
+ Mobilize::Resque.set_worker_args_by_path(r.path,{'status'=>msg})
157
+ return true
158
+ end
159
+
160
+ def is_working?
161
+ r = self
162
+ Mobilize::Resque.active_paths.include?(r.path)
163
+ end
164
+
165
+ def is_due?
166
+ r = self.reload
167
+ return false if r.is_working?
168
+ last_due_time = Time.now.utc - Jobtracker.runner_read_freq
169
+ return true if r.last_run.nil? or r.last_run < last_due_time
170
+ end
171
+
172
+ def enqueue!
173
+ r = self
174
+ ::Resque::Job.create("mobilize",Runner,r.path,{})
175
+ return true
176
+ end
177
+ end
178
+ end