mobilize-base 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,14 +7,12 @@ module Mobilize
7
7
  field :active, type: Boolean #active, inactive
8
8
  field :schedule, type: String
9
9
  field :active_task, type: String
10
- field :tasks, type: Hash
10
+ field :tasks, type: String
11
11
  field :status, type: String
12
12
  field :last_error, type: String
13
13
  field :last_trace, type: String
14
14
  field :last_completed_at, type: Time
15
- field :read_handler, type: String
16
- field :write_handler, type: String
17
- field :files, type: String #name of sheet(s) on doc
15
+ field :datasets, type: String #name of data sources
18
16
  field :params, type: String #JSON
19
17
  field :destination, type: String #output destination - could be file, could be sheet
20
18
 
@@ -28,19 +26,38 @@ module Mobilize
28
26
  Mobilize::Resque.find_worker_by_mongo_id(j.id.to_s)
29
27
  end
30
28
 
31
- def param_sheet_dsts
29
+ def dataset_array
32
30
  j = self
33
31
  r = j.requestor
34
- j.param_sheets.split(",").map do |ps|
32
+ dsts = j.datasets.split(",").map{|dst| dst.strip}
33
+ dsts.map do |ps|
35
34
  #prepend jobspec title if there is no path separator
36
35
  full_ps = ps.index("/") ? ps : [r.jobspec_title,ps].join("/")
37
36
  #find or create dataset for this sheet
38
- dst = Dataset.find_or_create_by_handler_and_name("gsheeter",full_ps)
37
+ dst = Dataset.find_or_create_by_handler_and_name("gsheet",full_ps)
39
38
  dst.update_attributes(:requestor_id=>r.id.to_s) unless dst.requestor_id
40
39
  dst
41
40
  end
42
41
  end
43
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
54
+ end
55
+
56
+ def task_idx
57
+ j = self
58
+ j.task_array.index(j.active_task)
59
+ end
60
+
44
61
  def Job.find_by_name(name)
45
62
  Job.where(:name=>name).first
46
63
  end
@@ -58,24 +75,18 @@ module Mobilize
58
75
  #called by Resque
59
76
  def Job.perform(id,*args)
60
77
  j = Job.find(id)
61
- task_params = j.tasks[j.active_task]
78
+ r = j.requestor
79
+ handler,method_name = j.active_task.split(".")
62
80
  begin
63
81
  j.update_status(%{Starting #{j.active_task} task at #{Time.now.utc}})
64
- task_output = "Mobilize::#{task_params['handler'].humanize}".constantize.send(j.active_task,id)
82
+ task_output = "Mobilize::#{handler.humanize}".constantize.send("#{method_name}_by_job_id",id)
65
83
  #this allows user to return false if the stage didn't go as expected and needs to retry
66
84
  #e.g. tried to write to Google but all the accounts were in use
67
85
  return false if task_output == false
68
- task_output_dst_id = if task_output.to_s.length==24 and Dataset.find(task_output.to_s)
69
- #user has returned dst as output from task
70
- task_output
71
- else
72
- #store the output in a cache
73
- dst = Dataset.find_or_create_by_requestor_id_and_handler_and_name(j.requestor.id.to_s,'mongoer',"#{j.id.to_s}/#{j.active_task}")
74
- dst.write_cache(task_output.to_s)
75
- dst.id.to_s
76
- end
77
- j.tasks[j.active_task]['output_dst_id'] = task_output_dst_id
78
- if j.active_task == j.tasks.keys.last
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
79
90
  j.active_task = nil
80
91
  j.last_error = ""
81
92
  j.last_trace = ""
@@ -89,9 +100,8 @@ module Mobilize
89
100
  #put begin/rescue so all dependencies run
90
101
  dep_jobs.each{|dj| begin;dj.enqueue! unless dj.is_working?;rescue;end}
91
102
  else
92
- task_names = j.tasks.keys
93
- stage_idx = task_names.index(j.active_task) + 1
94
- j.active_task = j.tasks.keys[stage_idx]
103
+ #set next task
104
+ j.active_task = j.task_array[j.task_idx+1]
95
105
  j.save!
96
106
  #queue up next task
97
107
  j.enqueue!
@@ -111,14 +121,9 @@ module Mobilize
111
121
 
112
122
  def enqueue!
113
123
  j = self
114
- #assign first task if none assigned
115
- if j.tasks.blank?
116
- #make a hash with the read/write tasks
117
- j.update_attributes(:tasks=>{"read_by_job_id"=>{'handler'=>j.read_handler},
118
- "write_by_job_id"=>{'handler'=>j.write_handler}})
119
- end
120
- j.update_attributes(:active_task=>"read_by_job_id") if j.active_task.blank?
121
- ::Resque::Job.create("mobilize",Job,j.id.to_s,%{#{j.requestor.name}=>#{j.name}})
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}})
122
127
  return true
123
128
  end
124
129
 
@@ -137,23 +142,23 @@ module Mobilize
137
142
  def prior_task
138
143
  j = self
139
144
  return nil if j.active_task.nil?
140
- task_idx = j.tasks.keys.index(j.active_task)
145
+ task_idx = j.task_array.index(j.active_task)
141
146
  return nil if task_idx==0
142
- return j.tasks.keys[task_idx-1]
147
+ return j.task_array[task_idx-1]
143
148
  end
144
149
 
145
150
  def destination_url
146
151
  j = self
147
152
  return nil if j.destination.nil?
148
153
  destination = j.destination
149
- dst = if j.write_handler == 'gsheet'
154
+ dst = if j.task_array.last == 'gsheet.write'
150
155
  destination = [j.requestor.jobspec_title,j.destination].join("/") if destination.split("/").length==1
151
- Dataset.find_by_handler_and_name('gsheeter',destination)
152
- elsif j.write_handler == 'gtxt'
153
- #all gtxt files must end in gz
156
+ Dataset.find_by_handler_and_name('gsheet',destination)
157
+ elsif j.task_array.last == 'gfile.write'
158
+ #all gfiles must end in gz
154
159
  destination += ".gz" unless destination.ends_with?(".gz")
155
160
  destination = [s.requestor.name,"_"].join + destination unless destination.starts_with?([s.requestor.name,"_"].join)
156
- Dataset.find_by_handler_and_name('gtxter',destination)
161
+ Dataset.find_by_handler_and_name('gfile',destination)
157
162
  end
158
163
  return dst.url if dst
159
164
  end
@@ -168,23 +173,6 @@ module Mobilize
168
173
  Jobtracker.set_worker_args(j.worker,args)
169
174
  end
170
175
 
171
- def cache_params
172
- j = self
173
- #go to paramsheet and read
174
- param_path = if j.paramsheet.split("/").length==1
175
- [j.requestor.jobspec_title,j.paramsheet].join("/")
176
- else
177
- j.paramsheet
178
- end
179
- param_sheet = j.requestor.find_or_create_gsheet_by_path(param_path)
180
- param_tsv = param_sheet.to_tsv
181
- param_dst = j.requestor.gsheets.select{|s| s.path == param_sheet.path}
182
- param_dst.cache.write(param_tsv)
183
- s.update_attributes(:param_dst_id=>paramdst.id.to_s)
184
- (s.requestor.name + "'s #{s.name} params cached").oputs
185
- return true
186
- end
187
-
188
176
  def update_status(msg)
189
177
  j = self
190
178
  j.update_attributes(:status=>msg)
@@ -31,13 +31,13 @@ module Mobilize
31
31
  end
32
32
 
33
33
  def Requestor.jobs_sheet_headers
34
- %w{name active schedule status last_error destination_url read_handler write_handler param_sheets params destination}
34
+ %w{name active schedule status last_error destination_url tasks datasets params destination}
35
35
  end
36
36
 
37
37
  def Requestor.perform(id,*args)
38
38
  r = Requestor.find(id.to_s)
39
39
  #reserve email account for read
40
- gdrive_email = Gdriver.get_worker_email_by_mongo_id(id)
40
+ gdrive_email = Gdrive.get_worker_email_by_mongo_id(id)
41
41
  unless gdrive_email
42
42
  "no gdrive_email available for #{r.name}".oputs
43
43
  return false
@@ -56,12 +56,12 @@ module Mobilize
56
56
  r.jobs.each do |j|
57
57
  begin
58
58
  if j.active and j.is_due?
59
- #cache all param_sheets
60
- j.param_sheet_dsts.each do |psdst|
59
+ #cache all datasets
60
+ j.dataset_array.each do |dst|
61
61
  #read tsv, write to cache for job to use
62
- tsv = Gsheeter.find_or_create_by_name(psdst.name,gdrive_email).to_tsv
63
- r.update_status("caching #{psdst.name}")
64
- psdst.write_cache(tsv)
62
+ tsv = Gsheet.find_or_create_by_name(dst.name,gdrive_email).to_tsv
63
+ r.update_status("caching #{dst.name}")
64
+ dst.write_cache(tsv)
65
65
  end
66
66
  j.enqueue!
67
67
  end
@@ -90,14 +90,13 @@ module Mobilize
90
90
  loc_jobs = []
91
91
  rem_jobs.each_with_index do |rj,rj_i|
92
92
  #skip bad rows
93
- next if (rj['name'].to_s.first == "#" or ['name','schedule','read_handler','write_handler','active'].select{|c| rj[c].to_s.strip==""}.length>0)
93
+ next if (rj['name'].to_s.first == "#" or ['name','schedule','tasks','active'].select{|c| rj[c].to_s.strip==""}.length>0)
94
94
  j = Job.find_or_create_by_requestor_id_and_name(r.id.to_s,rj['name'])
95
95
  #update top line params
96
96
  j.update_attributes(:active => rj['active'],
97
97
  :schedule => rj['schedule'],
98
- :read_handler => rj['read_handler'],
99
- :write_handler => rj['write_handler'],
100
- :param_sheets => rj['param_sheets'],
98
+ :tasks => rj['tasks'],
99
+ :datasets => rj['datasets'],
101
100
  :params => rj['params'],
102
101
  :destination => rj['destination'])
103
102
  #update laststatus with "Created job for" if job is due
@@ -125,20 +124,19 @@ module Mobilize
125
124
  #go through each job, update relevant job with its params
126
125
  headers = Requestor.jobs_sheet_headers
127
126
  #write headers
128
- headers.each_with_index do |h,h_i|
129
- jobs_sheet[1,h_i+1] = h
130
- end
127
+ jobs_sheet.add_headers(headers)
131
128
  #write rows
132
129
  rem_jobs.each_with_index do |rj,rj_i|
133
130
  #skip bad rows
134
- next if (rj['name'].to_s.first == "#" or ['name','schedule','read_handler','write_handler','active'].select{|c| rj[c].to_s.strip==""}.length>0)
135
- j = r.jobs(rj['name'])
136
- #update active to false if this was a run once
137
- j.update_attributes(:active=>false) if j.schedule.to_s == 'once'
138
- jobs_sheet[rj_i+2,headers.index('active')+1] = j.active.to_s
139
- jobs_sheet[rj_i+2,headers.index('status')+1] = j.status.to_s.gsub("\n",";").gsub("\t"," ")
140
- jobs_sheet[rj_i+2,headers.index('last_error')+1] = j.last_error.to_s.gsub("\n",";").gsub("\t"," ")
141
- jobs_sheet[rj_i+2,headers.index('destination_url')+1] = j.destination_url.to_s
131
+ next if (rj['name'].to_s.first == "#" or ['name','schedule','tasks','active'].select{|c| rj[c].to_s.strip==""}.length>0)
132
+ if j = r.jobs(rj['name'])
133
+ #update active to false if this was a run once
134
+ j.update_attributes(:active=>false) if j.schedule.to_s == 'once'
135
+ jobs_sheet[rj_i+2,headers.index('active')+1] = j.active.to_s
136
+ jobs_sheet[rj_i+2,headers.index('status')+1] = j.status.to_s.gsub("\n",";").gsub("\t"," ")
137
+ jobs_sheet[rj_i+2,headers.index('last_error')+1] = j.last_error.to_s.gsub("\n",";").gsub("\t"," ")
138
+ jobs_sheet[rj_i+2,headers.index('destination_url')+1] = j.destination_url.to_s
139
+ end
142
140
  end
143
141
  jobs_sheet.save
144
142
  r.update_status(r.name + " jobs written")
@@ -166,18 +164,18 @@ module Mobilize
166
164
 
167
165
  def find_or_create_gbook_by_title(title,gdrive_email)
168
166
  r = self
169
- book_dst = Dataset.find_or_create_by_handler_and_name('gbooker',title)
167
+ book_dst = Dataset.find_or_create_by_handler_and_name('gbook',title)
170
168
  #give dst this requestor if none
171
169
  book_dst.update_attributes(:requestor_id=>r.id.to_s) if book_dst.requestor_id.nil?
172
- book = Gbooker.find_or_create_by_dst_id(book_dst.id.to_s,gdrive_email)
170
+ book = Gbook.find_or_create_by_dst_id(book_dst.id.to_s,gdrive_email)
173
171
  return book
174
172
  end
175
173
 
176
174
  def find_or_create_gsheet_by_name(name,gdrive_email)
177
175
  r = self
178
- sheet_dst = Dataset.find_or_create_by_handler_and_name('gsheeter',name)
176
+ sheet_dst = Dataset.find_or_create_by_handler_and_name('gsheet',name)
179
177
  sheet_dst.update_attributes(:requestor_id=>r.id.to_s) if sheet_dst.requestor_id.nil?
180
- sheet = Gsheeter.find_or_create_by_dst_id(sheet_dst.id.to_s,gdrive_email)
178
+ sheet = Gsheet.find_or_create_by_dst_id(sheet_dst.id.to_s,gdrive_email)
181
179
  return sheet
182
180
  end
183
181
 
@@ -230,6 +228,5 @@ module Mobilize
230
228
  ::Resque::Job.create("mobilize",Requestor,r.id.to_s,{"name"=>r.name})
231
229
  return true
232
230
  end
233
-
234
231
  end
235
232
  end
@@ -1,5 +1,5 @@
1
1
  module Mobilize
2
2
  module Base
3
- VERSION = "1.0.1"
3
+ VERSION = "1.0.2"
4
4
  end
5
5
  end
data/lib/mobilize-base.rb CHANGED
@@ -67,10 +67,10 @@ require "mobilize-base/extensions/resque"
67
67
  Resque.redis = "127.0.0.1:#{Mobilize::Resque.config['redis_port']}"
68
68
  require 'popen4'
69
69
  require "mobilize-base/jobtracker"
70
- require "mobilize-base/handlers/gdriver"
71
- require "mobilize-base/extensions/google_drive.rb"
72
- require "mobilize-base/handlers/mongoer"
73
- require "mobilize-base/handlers/emailer"
74
-
75
- #require "mobilize-base/handlers/*"
76
- #require "mobilize-base/models/*"
70
+ require "mobilize-base/handlers/gdrive"
71
+ require "mobilize-base/handlers/gfile"
72
+ require "mobilize-base/handlers/gbook"
73
+ require "mobilize-base/handlers/gsheet"
74
+ require "mobilize-base/extensions/google_drive"
75
+ require "mobilize-base/handlers/mongodb"
76
+ require "mobilize-base/handlers/email"
@@ -1,4 +1,5 @@
1
1
  development:
2
+ domain: 'host.com'
2
3
  owner:
3
4
  email: 'owner_development@host.com'
4
5
  pw: "google_drive_password"
@@ -8,6 +9,7 @@ development:
8
9
  - {email: 'worker_development001@host.com', pw: "worker001_google_drive_password"}
9
10
  - {email: 'worker_development002@host.com', pw: "worker002_google_drive_password"}
10
11
  test:
12
+ domain: 'host.com'
11
13
  owner:
12
14
  email: 'owner_test@host.com'
13
15
  pw: "google_drive_password"
@@ -17,6 +19,7 @@ test:
17
19
  - {email: 'worker_test001@host.com', pw: "worker001_google_drive_password"}
18
20
  - {email: 'worker_test002@host.com', pw: "worker002_google_drive_password"}
19
21
  production:
22
+ domain: 'host.com'
20
23
  owner:
21
24
  email: 'owner_production@host.com'
22
25
  pw: "google_drive_password"
@@ -3,20 +3,19 @@ require 'test_helper'
3
3
  describe "Mobilize" do
4
4
 
5
5
  def before
6
- puts 'before'
7
-
6
+ puts 'nothing before'
8
7
  end
9
8
 
10
9
  # enqueues 4 workers on Resque
11
10
  it "runs integration test" do
11
+
12
+ puts "restart test redis"
13
+ Mobilize::Jobtracker.restart_test_redis
14
+
12
15
  puts "clear out test db"
13
- Mongoid.session(:default).collections.each do |collection|
14
- unless collection.name =~ /^system\./
15
- collection.drop
16
- end
17
- end
16
+ Mobilize::Jobtracker.drop_test_db
18
17
 
19
- email = Mobilize::Gdriver.owner_email
18
+ email = Mobilize::Gdrive.owner_email
20
19
 
21
20
  #kill all workers
22
21
  Mobilize::Jobtracker.kill_workers
@@ -31,9 +30,9 @@ describe "Mobilize" do
31
30
  assert requestor.email == email
32
31
 
33
32
  puts "delete old books and datasets"
34
- # delete any old specbooks from previous test runs
33
+ # delete any old jobspec from previous test runs
35
34
  jobspec_title = requestor.jobspec_title
36
- books = Mobilize::Gbooker.find_all_by_title(jobspec_title)
35
+ books = Mobilize::Gbook.find_all_by_title(jobspec_title)
37
36
  books.each{|book| book.delete}
38
37
 
39
38
  puts "enqueue jobtracker, wait 45s"
@@ -42,12 +41,12 @@ describe "Mobilize" do
42
41
  puts "jobtracker status: #{Mobilize::Jobtracker.status}"
43
42
  puts "status:#{Mobilize::Jobtracker.status}" #!= 'stopped'
44
43
 
45
- puts "requestor created specbook?"
46
- books = Mobilize::Gbooker.find_all_by_title(jobspec_title)
44
+ puts "requestor created jobspec?"
45
+ books = Mobilize::Gbook.find_all_by_title(jobspec_title)
47
46
  assert books.length == 1
48
47
 
49
48
  puts "Jobtracker created jobspec with 'jobs' sheet?"
50
- jobs_sheets = Mobilize::Gsheeter.find_all_by_name("#{jobspec_title}/Jobs",email)
49
+ jobs_sheets = Mobilize::Gsheet.find_all_by_name("#{jobspec_title}/Jobs",email)
51
50
  assert jobs_sheets.length == 1
52
51
 
53
52
  puts "add test_source data"
@@ -59,12 +58,12 @@ describe "Mobilize" do
59
58
  ]
60
59
 
61
60
  book = books.first
62
- test_source_sheet = Mobilize::Gsheeter.find_or_create_by_name("#{jobspec_title}/test_source",email)
61
+ test_source_sheet = Mobilize::Gsheet.find_or_create_by_name("#{jobspec_title}/test_source",email)
63
62
 
64
63
  test_source_tsv = test_source_rows.map{|r| r.join("\t")}.join("\n")
65
64
  test_source_sheet.write(test_source_tsv)
66
65
 
67
- puts "add row to jobs sheet, wait 60s"
66
+ puts "add row to jobs sheet, wait 120s"
68
67
 
69
68
  jobs_sheet = jobs_sheets.first
70
69
 
@@ -74,9 +73,8 @@ describe "Mobilize" do
74
73
  "status" => "",
75
74
  "last_error" => "",
76
75
  "destination_url" => "",
77
- "read_handler" => "gsheeter",
78
- "write_handler" => "gsheeter",
79
- "param_sheets" => "test_source",
76
+ "tasks" => "gsheet.read, gsheet.write",
77
+ "datasets" => "test_source",
80
78
  "params" => "",
81
79
  "destination" => "test_destination"},
82
80
  #run after the first
@@ -86,39 +84,25 @@ describe "Mobilize" do
86
84
  "status" => "",
87
85
  "last_error" => "",
88
86
  "destination_url" => "",
89
- "read_handler" => "gsheeter",
90
- "write_handler" => "gsheeter",
91
- "param_sheets" => "test_source",
87
+ "tasks" => "gsheet.read, gsheet.write",
88
+ "datasets" => "test_source",
92
89
  "params" => "",
93
90
  "destination" => "test_destination2"}
94
91
  ]
95
92
 
96
- #update second row w details
97
- test_job_rows.each_with_index do |r,r_i|
98
- r.values.each_with_index do |v,v_i|
99
- jobs_sheet[r_i+2,v_i+1] = v
100
- end
101
- end
102
-
103
- jobs_sheet.save
93
+ jobs_sheet.add_or_update_rows(test_job_rows)
104
94
 
105
95
  puts "job row added, force enqueued requestor"
106
96
  requestor.enqueue!
107
- sleep 60
97
+ sleep 120
108
98
 
109
99
  puts "jobtracker posted test sheet data to test destination, and checksum succeeded?"
110
- test_destination_sheet = Mobilize::Gsheeter.find_or_create_by_name("#{jobspec_title}/test_destination",email)
100
+ test_destination_sheet = Mobilize::Gsheet.find_or_create_by_name("#{jobspec_title}/test_destination",email)
111
101
 
112
102
  assert test_destination_sheet.to_tsv == test_source_sheet.to_tsv
113
- end
114
103
 
115
- after do
116
- processes = `ps -A -o pid,command | grep [r]edis-test`.split($/)
117
- pids = processes.map { |process| process.split(" ")[0] }
118
- puts "Killing test redis server..."
119
- pids.each { |pid| Process.kill("TERM", pid.to_i) }
120
- puts "removing redis db dump file"
121
- sleep 5
122
- `rm -f #{$dir}/dump.rdb #{$dir}/dump-cluster.rdb`
104
+ puts "stop test redis"
105
+ Mobilize::Jobtracker.stop_test_redis
123
106
  end
107
+
124
108
  end
data/test/test_helper.rb CHANGED
@@ -8,16 +8,3 @@ $dir = File.dirname(File.expand_path(__FILE__))
8
8
  ENV['MOBILIZE_ENV'] = 'test'
9
9
  require 'mobilize-base'
10
10
  $TESTING = true
11
-
12
- #
13
- # make sure we can run redis
14
- #
15
-
16
- if !system("which redis-server")
17
- puts '', "** can't find `redis-server` in your path, you need redis to run Resque and Mobilize"
18
- abort ''
19
- end
20
-
21
- #start test redis
22
- puts "Starting redis for testing at 127.0.0.1:#{Mobilize::Resque.config['redis_port']}..."
23
- `redis-server #{$dir}/redis-test.conf`
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobilize-base
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-27 00:00:00.000000000 Z
12
+ date: 2012-11-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -210,9 +210,12 @@ files:
210
210
  - lib/mobilize-base/extensions/object.rb
211
211
  - lib/mobilize-base/extensions/resque.rb
212
212
  - lib/mobilize-base/extensions/string.rb
213
- - lib/mobilize-base/handlers/emailer.rb
214
- - lib/mobilize-base/handlers/gdriver.rb
215
- - lib/mobilize-base/handlers/mongoer.rb
213
+ - lib/mobilize-base/handlers/email.rb
214
+ - lib/mobilize-base/handlers/gbook.rb
215
+ - lib/mobilize-base/handlers/gdrive.rb
216
+ - lib/mobilize-base/handlers/gfile.rb
217
+ - lib/mobilize-base/handlers/gsheet.rb
218
+ - lib/mobilize-base/handlers/mongodb.rb
216
219
  - lib/mobilize-base/jobtracker.rb
217
220
  - lib/mobilize-base/models/dataset.rb
218
221
  - lib/mobilize-base/models/job.rb