mobilize-base 1.0.1 → 1.0.2

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.
@@ -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