mobilize-base 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +509 -0
- data/Rakefile +34 -0
- data/lib/mobilize-base/extensions/array.rb +22 -0
- data/lib/mobilize-base/extensions/google_drive.rb +296 -0
- data/lib/mobilize-base/extensions/hash.rb +86 -0
- data/lib/mobilize-base/extensions/object.rb +6 -0
- data/lib/mobilize-base/extensions/resque.rb +180 -0
- data/lib/mobilize-base/extensions/string.rb +94 -0
- data/lib/mobilize-base/handlers/emailer.rb +24 -0
- data/lib/mobilize-base/handlers/gdriver.rb +309 -0
- data/lib/mobilize-base/handlers/mongoer.rb +32 -0
- data/lib/mobilize-base/jobtracker.rb +208 -0
- data/lib/mobilize-base/models/dataset.rb +70 -0
- data/lib/mobilize-base/models/job.rb +253 -0
- data/lib/mobilize-base/models/requestor.rb +223 -0
- data/lib/mobilize-base/tasks/mobilize-base.rake +2 -0
- data/lib/mobilize-base/tasks.rb +43 -0
- data/lib/mobilize-base/version.rb +5 -0
- data/lib/mobilize-base.rb +76 -0
- data/lib/samples/gdrive.yml +27 -0
- data/lib/samples/jobtracker.yml +24 -0
- data/lib/samples/mongoid.yml +21 -0
- data/lib/samples/resque.yml +12 -0
- data/mobilize-base.gemspec +35 -0
- data/test/mobilize_test.rb +125 -0
- data/test/redis-test.conf +540 -0
- data/test/test_helper.rb +23 -0
- metadata +260 -0
@@ -0,0 +1,296 @@
|
|
1
|
+
module GoogleDrive
|
2
|
+
class ClientLoginFetcher
|
3
|
+
def request_raw(method, url, data, extra_header, auth)
|
4
|
+
#this is patched to handle server errors due to http chaos
|
5
|
+
uri = URI.parse(url)
|
6
|
+
response = nil
|
7
|
+
attempts = 0
|
8
|
+
sleep_time = nil
|
9
|
+
#try 5 times to make the call
|
10
|
+
while (response.nil? or response.code.ie{|rcode| rcode.starts_with?("4") or rcode.starts_with?("5")}) and attempts < 5
|
11
|
+
#instantiate http object, set params
|
12
|
+
http = @proxy.new(uri.host, uri.port)
|
13
|
+
http.use_ssl = true
|
14
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
15
|
+
#set 600 to allow for large downloads
|
16
|
+
http.read_timeout = 600
|
17
|
+
response = self.http_call(http, method, uri, data, extra_header, auth)
|
18
|
+
if response.code.ie{|rcode| rcode.starts_with?("4") or rcode.starts_with?("5")}
|
19
|
+
if response.body.downcase.index("rate limit") or response.body.downcase.index("captcha")
|
20
|
+
if sleep_time
|
21
|
+
sleep_time = sleep_time * attempts
|
22
|
+
else
|
23
|
+
sleep_time = (rand*100).to_i
|
24
|
+
end
|
25
|
+
else
|
26
|
+
sleep_time = 10
|
27
|
+
end
|
28
|
+
attempts += 1
|
29
|
+
puts "Sleeping for #{sleep_time.to_s} due to #{response.body}"
|
30
|
+
sleep sleep_time
|
31
|
+
end
|
32
|
+
end
|
33
|
+
raise response.body if response.code.ie{|rcode| rcode.starts_with?("4") or rcode.starts_with?("5")}
|
34
|
+
return response
|
35
|
+
end
|
36
|
+
def http_call(http, method, uri, data, extra_header, auth)
|
37
|
+
http.read_timeout = 600
|
38
|
+
http.start() do
|
39
|
+
path = uri.path + (uri.query ? "?#{uri.query}" : "")
|
40
|
+
header = auth_header(auth).merge(extra_header)
|
41
|
+
if method == :delete || method == :get
|
42
|
+
http.__send__(method, path, header)
|
43
|
+
else
|
44
|
+
http.__send__(method, path, data, header)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
class Acl
|
50
|
+
def update_role(entry, role) #:nodoc:
|
51
|
+
#do not send email notifications
|
52
|
+
url_suffix = "?send-notification-emails=false"
|
53
|
+
header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
|
54
|
+
doc = @session.request(
|
55
|
+
:put, %{#{entry.edit_url}#{url_suffix}}, :data => entry.to_xml(), :header => header, :auth => :writely)
|
56
|
+
|
57
|
+
entry.params = entry_to_params(doc.root)
|
58
|
+
return entry
|
59
|
+
end
|
60
|
+
|
61
|
+
def push(entry)
|
62
|
+
#do not send email notifications
|
63
|
+
entry = AclEntry.new(entry) if entry.is_a?(Hash)
|
64
|
+
url_suffix = "?send-notification-emails=false"
|
65
|
+
header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
|
66
|
+
doc = @session.request(:post, "#{@acls_feed_url}#{url_suffix}", :data => entry.to_xml(), :header => header, :auth => :writely)
|
67
|
+
entry.params = entry_to_params(doc.root)
|
68
|
+
@acls.push(entry)
|
69
|
+
return entry
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class File
|
74
|
+
|
75
|
+
def add_worker_acl
|
76
|
+
f = self
|
77
|
+
return true if f.has_worker_acl?
|
78
|
+
Mobilize::Gdriver.worker_emails.each do |a|
|
79
|
+
f.update_acl(a)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_admin_acl
|
84
|
+
f = self
|
85
|
+
#admin includes workers
|
86
|
+
return true if f.has_admin_acl?
|
87
|
+
(Mobilize::Gdriver.admin_emails + Mobilize::Gdriver.worker_emails).each do |a|
|
88
|
+
f.update_acl(a)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def has_admin_acl?
|
93
|
+
f = self
|
94
|
+
curr_emails = f.acls.map{|a| a.scope}.sort
|
95
|
+
admin_emails = Mobilize::Gdriver.admin_emails.sort
|
96
|
+
if (curr_emails & admin_emails) == admin_emails
|
97
|
+
return true
|
98
|
+
else
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def has_worker_acl?
|
104
|
+
f = self
|
105
|
+
curr_emails = f.acls.map{|a| a.scope}.sort
|
106
|
+
worker_emails = Mobilize::Gdriver.worker_emails.sort
|
107
|
+
if (curr_emails & worker_emails) == worker_emails
|
108
|
+
return true
|
109
|
+
else
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_acl(email,role="writer")
|
115
|
+
f = self
|
116
|
+
#need these flags for HTTP retries
|
117
|
+
#create req_acl hash to add to current acl
|
118
|
+
if entry = f.acl_entry(email)
|
119
|
+
if [nil,"none","delete"].include?(role)
|
120
|
+
f.acl.delete(entry)
|
121
|
+
elsif entry.role != role and ['reader','writer','owner'].include?(role)
|
122
|
+
entry.role=role
|
123
|
+
f.acl.update_role(entry,entry.role)
|
124
|
+
elsif !['reader','writer','owner'].include?(role)
|
125
|
+
raise "Invalid role #{role}"
|
126
|
+
end
|
127
|
+
else
|
128
|
+
f.acl.push({:scope_type=>"user",:scope=>email,:role=>role})
|
129
|
+
end
|
130
|
+
return true
|
131
|
+
end
|
132
|
+
def acls
|
133
|
+
f = self
|
134
|
+
f.acl.to_enum.to_a
|
135
|
+
end
|
136
|
+
def acl_entry(email)
|
137
|
+
f = self
|
138
|
+
f.acls.select{|a| ['group','user'].include?(a.scope_type) and a.scope == email}.first
|
139
|
+
end
|
140
|
+
|
141
|
+
def entry_hash
|
142
|
+
f = self
|
143
|
+
dfe_xml = f.document_feed_entry.to_xml
|
144
|
+
begin
|
145
|
+
Hash.from_xml(dfe_xml)[:entry]
|
146
|
+
rescue
|
147
|
+
{}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Worksheet
|
153
|
+
def to_tsv
|
154
|
+
sheet = self
|
155
|
+
rows = sheet.rows
|
156
|
+
header = rows.first
|
157
|
+
return nil unless header and header.first.to_s.length>0
|
158
|
+
#look for blank cols to indicate end of row
|
159
|
+
row_last_i = (header.index("") || header.length)-1
|
160
|
+
rows.map{|r| r[0..row_last_i]}.map{|r| r.join("\t")}.join("\n")
|
161
|
+
end
|
162
|
+
def write(tsv,check=true,job_id=nil)
|
163
|
+
sheet = self
|
164
|
+
tsvrows = tsv.split("\n")
|
165
|
+
#no rows, no write
|
166
|
+
return true if tsvrows.length==0
|
167
|
+
headers = tsvrows.first.split("\t")
|
168
|
+
batch_start = 0
|
169
|
+
batch_length = 80
|
170
|
+
rows_written = 0
|
171
|
+
curr_rows = sheet.num_rows
|
172
|
+
curr_cols = sheet.num_cols
|
173
|
+
pct_tens_complete =["0"]
|
174
|
+
curr_pct_complete = "00"
|
175
|
+
#make sure sheet is at least as big as necessary
|
176
|
+
if tsvrows.length != curr_rows
|
177
|
+
sheet.max_rows = tsvrows.length
|
178
|
+
sheet.save
|
179
|
+
end
|
180
|
+
if headers.length != curr_cols
|
181
|
+
sheet.max_cols = headers.length
|
182
|
+
sheet.save
|
183
|
+
end
|
184
|
+
#write to sheet in batches of batch_length
|
185
|
+
while batch_start < tsvrows.length
|
186
|
+
batch_end = batch_start + batch_length
|
187
|
+
tsvrows[batch_start..batch_end].each_with_index do |row,row_i|
|
188
|
+
rowcols = row.split("\t")
|
189
|
+
rowcols.each_with_index do |col_v,col_i|
|
190
|
+
sheet[row_i+batch_start+1,col_i+1]= %{#{col_v}}
|
191
|
+
end
|
192
|
+
end
|
193
|
+
sheet.save
|
194
|
+
batch_start += (batch_length + 1)
|
195
|
+
rows_written+=batch_length
|
196
|
+
if batch_start>tsvrows.length+1
|
197
|
+
if job_id
|
198
|
+
newstatus = "100 pct written at #{Time.now.utc}"
|
199
|
+
Mobilize::Job.find(job_id).update_status(newstatus)
|
200
|
+
newstatus.oputs
|
201
|
+
end
|
202
|
+
break
|
203
|
+
else
|
204
|
+
#pad digit
|
205
|
+
curr_pct_complete = "%02d" % ((rows_written+1).to_f*100/tsvrows.length.to_f).round(0)
|
206
|
+
if !pct_tens_complete.include?(curr_pct_complete.first)
|
207
|
+
if job_id
|
208
|
+
newstatus = "#{curr_pct_complete} pct written at #{Time.now.utc}"
|
209
|
+
Mobilize::Job.find(job_id).update_status(newstatus)
|
210
|
+
newstatus.oputs
|
211
|
+
pct_tens_complete << curr_pct_complete.first
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
#checksum it against the source
|
217
|
+
sheet.checksum(tsv) if check
|
218
|
+
true
|
219
|
+
end
|
220
|
+
def checksum(tsv)
|
221
|
+
sheet = self
|
222
|
+
sheet.reload
|
223
|
+
#loading remote data for checksum
|
224
|
+
rem_tsv = sheet.to_tsv
|
225
|
+
rem_table = rem_tsv.split("\n").map{|r| r.split("\t").map{|v| v.googlesafe}}
|
226
|
+
loc_table = tsv.split("\n").map{|r| r.split("\t").map{|v| v.googlesafe}}
|
227
|
+
re_col_vs = []
|
228
|
+
errcnt = 0
|
229
|
+
#checking cells
|
230
|
+
loc_table.each_with_index do |loc_row,row_i|
|
231
|
+
loc_row.each_with_index do |loc_v,col_i|
|
232
|
+
rem_row = rem_table[row_i]
|
233
|
+
if rem_row.nil?
|
234
|
+
errcnt+=1
|
235
|
+
"No Row #{row_i} for Write Dst".oputs
|
236
|
+
break
|
237
|
+
else
|
238
|
+
rem_v = rem_table[row_i][col_i]
|
239
|
+
if loc_v != rem_v
|
240
|
+
if ['true','false'].include?(loc_v.downcase)
|
241
|
+
#google sheet upcases true and false. ignore
|
242
|
+
elsif loc_v.starts_with?('rp') and rem_v.starts_with?('Rp')
|
243
|
+
# some other math bs
|
244
|
+
sheet[row_i+1,col_i+1] = %{'#{loc_v}}
|
245
|
+
re_col_vs << {'row_i'=>row_i+1,'col_i'=>col_i+1,'col_v'=>%{'#{loc_v}}}
|
246
|
+
elsif (loc_v.to_s.count('e')==1 or loc_v.to_s.count('e')==0) and
|
247
|
+
loc_v.to_s.sub('e','').to_i.to_s==loc_v.to_s.sub('e','').gsub(/\A0+/,"") #trim leading zeroes
|
248
|
+
#this is a string in scentific notation, or a numerical string with a leading zero
|
249
|
+
#GDocs handles this poorly, need to rewrite write_dst cells by hand with a leading apostrophe for text
|
250
|
+
sheet[row_i+1,col_i+1] = %{'#{loc_v}}
|
251
|
+
re_col_vs << {'row_i'=>row_i+1,'col_i'=>col_i+1,'col_v'=>%{'#{loc_v}}}
|
252
|
+
elsif loc_v.class==Float or loc_v.class==Fixnum
|
253
|
+
if (loc_v - rem_v.to_f).abs>0.0001
|
254
|
+
"row #{row_i.to_s} col #{col_i.to_s}: Local=>#{loc_v.to_s} , Remote=>#{rem_v.to_s}".oputs
|
255
|
+
errcnt+=1
|
256
|
+
end
|
257
|
+
elsif rem_v.class==Float or rem_v.class==Fixnum
|
258
|
+
if (rem_v - loc_v.to_f).abs>0.0001
|
259
|
+
"row #{row_i.to_s} col #{col_i.to_s}: Local=>#{loc_v.to_s} , Remote=>#{rem_v.to_s}".oputs
|
260
|
+
errcnt+=1
|
261
|
+
end
|
262
|
+
elsif loc_v.to_s.is_time?
|
263
|
+
rem_time = begin
|
264
|
+
Time.parse(rem_v.to_s)
|
265
|
+
rescue
|
266
|
+
nil
|
267
|
+
end
|
268
|
+
if rem_time.nil? || ((loc_v - rem_time).abs>1)
|
269
|
+
"row #{row_i.to_s} col #{col_i.to_s}: Local=>#{loc_v} , Remote=>#{rem_v}".oputs
|
270
|
+
errcnt+=1
|
271
|
+
end
|
272
|
+
else
|
273
|
+
#"loc_v=>#{loc_v.to_s},rem_v=>#{rem_v.to_s}".oputs
|
274
|
+
if loc_v.force_encoding("UTF-8") != rem_v.force_encoding("UTF-8")
|
275
|
+
#make sure it's not an ecoding issue
|
276
|
+
"row #{row_i.to_s} col #{col_i.to_s}: Local=>#{loc_v} , Remote=>#{rem_v}".oputs
|
277
|
+
errcnt+=1
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
if errcnt==0
|
285
|
+
if re_col_vs.length>0
|
286
|
+
sheet.save
|
287
|
+
"rewrote:#{re_col_vs.to_s}".oputs
|
288
|
+
else
|
289
|
+
true
|
290
|
+
end
|
291
|
+
else
|
292
|
+
raise "#{errcnt} errors found in checksum"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
|
2
|
+
class Hash
|
3
|
+
#defined read and write methods for hashes to get around annoying mkpath issues
|
4
|
+
def read_path(array)
|
5
|
+
result=self
|
6
|
+
array.each_with_index do |p,i|
|
7
|
+
if result.class==Hash and result[p]
|
8
|
+
result=result[p]
|
9
|
+
elsif i==array.length-1
|
10
|
+
return result[p]
|
11
|
+
else
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
return result
|
16
|
+
end
|
17
|
+
#write array of arbitrary depth to the hash
|
18
|
+
def write_path(array,data)
|
19
|
+
return self[array.first.to_s]=data if array.length==1
|
20
|
+
array.each_with_index do |m,i|
|
21
|
+
arr_s = array[0..i]
|
22
|
+
if !self.read_path(arr_s)
|
23
|
+
eval(%{self["#{arr_s.join('"]["')}"]={}})
|
24
|
+
elsif self.read_path(arr_s).class != Hash
|
25
|
+
raise "There is data at #{arr_s.join("=>")}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
eval(%{self["#{array[0..-2].join('"]["')}"]}).store(array[-1].to_s,data)
|
29
|
+
return self
|
30
|
+
end
|
31
|
+
# BEGIN methods to create hash from XML
|
32
|
+
class << self
|
33
|
+
def from_xml(xml_io)
|
34
|
+
begin
|
35
|
+
result = Nokogiri::XML(xml_io)
|
36
|
+
return { result.root.name.to_sym => xml_node_to_hash(result.root)}
|
37
|
+
rescue Exception => e
|
38
|
+
# raise your custom exception here
|
39
|
+
end
|
40
|
+
end
|
41
|
+
def xml_node_to_hash(node)
|
42
|
+
# If we are at the root of the document, start the hash
|
43
|
+
if node.element?
|
44
|
+
result_hash = {}
|
45
|
+
if node.attributes != {}
|
46
|
+
result_hash[:attributes] = {}
|
47
|
+
node.attributes.keys.each do |key|
|
48
|
+
result_hash[:attributes][node.attributes[key].name.to_sym] = prepare(node.attributes[key].value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
if node.children.size > 0
|
52
|
+
node.children.each do |child|
|
53
|
+
result = xml_node_to_hash(child)
|
54
|
+
|
55
|
+
if child.name == "text"
|
56
|
+
unless child.next_sibling || child.previous_sibling
|
57
|
+
return prepare(result)
|
58
|
+
end
|
59
|
+
elsif result_hash[child.name.to_sym]
|
60
|
+
if result_hash[child.name.to_sym].is_a?(Object::Array)
|
61
|
+
result_hash[child.name.to_sym] << prepare(result)
|
62
|
+
else
|
63
|
+
result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << prepare(result)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
result_hash[child.name.to_sym] = prepare(result)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
return result_hash
|
71
|
+
else
|
72
|
+
return result_hash
|
73
|
+
end
|
74
|
+
else
|
75
|
+
return prepare(node.content.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
def prepare(data)
|
79
|
+
(data.class == String && data.to_i.to_s == data) ? data.to_i : data
|
80
|
+
end
|
81
|
+
end
|
82
|
+
def to_struct(struct_name)
|
83
|
+
Struct.new(struct_name,*keys).new(*values)
|
84
|
+
end
|
85
|
+
# END methods to create hash from XML
|
86
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module Mobilize
|
2
|
+
module Resque
|
3
|
+
def Resque.config
|
4
|
+
Base.config('resque')[Base.env]
|
5
|
+
end
|
6
|
+
|
7
|
+
def Resque.queue_name
|
8
|
+
Resque.config['queue_name']
|
9
|
+
end
|
10
|
+
|
11
|
+
def Resque.queues
|
12
|
+
Base.queues
|
13
|
+
end
|
14
|
+
|
15
|
+
def Resque.log_path
|
16
|
+
Base.log_path("mobilize-resque-#{::Mobilize::Base.env}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def Resque.workers(state="all")
|
20
|
+
raise "invalid state #{state}" unless ['all','idle','working','timeout'].include?(state)
|
21
|
+
workers = ::Resque.workers.select{|w| w.queues.first == Resque.queue_name}
|
22
|
+
return workers if state == 'all'
|
23
|
+
working_workers = workers.select{|w| w.job['queue']== Resque.queue_name}
|
24
|
+
return working_workers if state == 'working'
|
25
|
+
idle_workers = workers.select{|w| w.job['queue'].nil?}
|
26
|
+
return idle_workers if state == 'idle'
|
27
|
+
timeout_workers = workers.select{|w| w.job['payload'] and w.job['payload']['class']!='Jobtracker' and w.job['runat'] < (Time.now.utc - Jobtracker.max_run_time)}
|
28
|
+
return timeout_workers if state == 'timeout'
|
29
|
+
end
|
30
|
+
|
31
|
+
def Resque.failures
|
32
|
+
::Resque::Failure.all(0,0).select{|f| f['queue'] == Resque.queue_name}
|
33
|
+
end
|
34
|
+
|
35
|
+
#active state refers to jobs that are either queued or working
|
36
|
+
def Resque.jobs(state="active")
|
37
|
+
raise "invalid state #{state}" unless ['all','queued','working','active','timeout','failed'].include?(state)
|
38
|
+
working_jobs = Resque.workers('working').map{|w| w.job['payload']}
|
39
|
+
return working_jobs if state == 'working'
|
40
|
+
queued_jobs = ::Resque.peek(Resque.queue_name,0,0).to_a
|
41
|
+
return queued_jobs if state == 'queued'
|
42
|
+
return working_jobs + queued_jobs if state == 'active'
|
43
|
+
failed_jobs = Resque.failures.map{|f| f['payload']}
|
44
|
+
return failed_jobs if state == 'failed'
|
45
|
+
timeout_jobs = Resque.workers("timeout").map{|w| w.job['payload']}
|
46
|
+
return timeout_jobs if state == 'timeout'
|
47
|
+
return working_jobs + queued_jobs + failed_jobs if state == 'all'
|
48
|
+
end
|
49
|
+
|
50
|
+
def Resque.active_mongo_ids
|
51
|
+
#first argument of the payload is the mongo id in Mongo unless the worker is Jobtracker
|
52
|
+
Resque.jobs('active').map{|j| j['args'].first unless j['class']=='Jobtracker'}.compact
|
53
|
+
end
|
54
|
+
|
55
|
+
#Resque workers and methods to find
|
56
|
+
def Resque.find_worker_by_mongo_id(mongo_id)
|
57
|
+
Resque.workers('working').select{|w| w.job['payload']['args'][0] == mongo_id}.first
|
58
|
+
end
|
59
|
+
|
60
|
+
def Resque.update_job_status(mongo_id,msg)
|
61
|
+
#this only works on working workers
|
62
|
+
worker = Resque.find_worker_by_mongo_id(mongo_id)
|
63
|
+
#also fire a log, cap logfiles at 10 MB
|
64
|
+
if !worker
|
65
|
+
Logger.new(Resque.log_path, 10, 1024*1000*10).info("[no worker for #{mongo_id}: #{Time.now.utc}] status: #{msg}")
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
Resque.set_worker_args(worker,{"status"=>msg})
|
69
|
+
Logger.new(Resque.log_path, 10, 1024*1000*10).info("[#{worker} #{Time.now.utc}] status: #{msg}")
|
70
|
+
return true
|
71
|
+
end
|
72
|
+
|
73
|
+
def Resque.update_job_email(mongo_id,email)
|
74
|
+
#this only works on working workers
|
75
|
+
worker = Resque.find_worker_by_mongo_id(mongo_id)
|
76
|
+
return false unless worker
|
77
|
+
Resque.set_worker_args(worker,{"email"=>email})
|
78
|
+
#also fire a log, cap logfiles at 10 MB
|
79
|
+
Logger.new(Resque.log_path, 10, 1024*1000*10).info("[#{worker} #{Time.now.utc}] email: #{email}")
|
80
|
+
end
|
81
|
+
|
82
|
+
def Resque.get_worker_args(worker)
|
83
|
+
key = "worker:#{worker}"
|
84
|
+
json = ::Resque.redis.get(key)
|
85
|
+
if json
|
86
|
+
hash = JSON.parse(json)
|
87
|
+
hash['payload']['args'].last
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
#takes a worker and invokes redis to set the last value in its second arg array element
|
92
|
+
#by our convention this is a Hash
|
93
|
+
def Resque.set_worker_args(worker,args)
|
94
|
+
key = "worker:#{worker}"
|
95
|
+
json = ::Resque.redis.get(key)
|
96
|
+
if json
|
97
|
+
hash = JSON.parse(json)
|
98
|
+
payload_args = hash['payload']['args']
|
99
|
+
#jobmaster only gets one arg
|
100
|
+
if payload_args[1].nil?
|
101
|
+
payload_args[1] = args
|
102
|
+
else
|
103
|
+
payload_args[1] = {} unless payload_args[1].class==Hash
|
104
|
+
args.keys.each{|k,v| payload_args[1][k] = args[k]}
|
105
|
+
end
|
106
|
+
::Resque.redis.set(key,hash.to_json)
|
107
|
+
return true
|
108
|
+
else
|
109
|
+
return false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def Resque.failure_report
|
114
|
+
fjobs = {}
|
115
|
+
excs = Hash.new(0)
|
116
|
+
Resque.failures.each do |f|
|
117
|
+
sname = f['payload']['class'] + ("=>" + f['payload']['args'].second['name'].to_s if f['payload']['args'].second).to_s
|
118
|
+
excs = f['error']
|
119
|
+
if fjobs[sname].nil?
|
120
|
+
fjobs[sname] = {excs => 1}
|
121
|
+
elsif fjobs[sname][excs].nil?
|
122
|
+
fjobs[sname][excs] = 1
|
123
|
+
else
|
124
|
+
fjobs[sname][excs] += 1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
return fjobs
|
128
|
+
end
|
129
|
+
|
130
|
+
def Resque.start_workers(count=1)
|
131
|
+
count.times do
|
132
|
+
"(cd #{Base.root};rake MOBILIZE_ENV=#{Base.env} mobilize:work) >> #{Resque.log_path} 2>&1 &".bash
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def Resque.kill_idle_workers(count=nil)
|
137
|
+
idle_pids = Resque.workers('idle').select{|w| w.job=={}}.map{|w| w.to_s.split(":").second}
|
138
|
+
if count>idle_pids.length or count == 0
|
139
|
+
return false
|
140
|
+
elsif count
|
141
|
+
"kill #{idle_pids[0..count-1].join(" ")}".bash
|
142
|
+
else
|
143
|
+
"kill #{idle_pids.join(" ")}".bash
|
144
|
+
end
|
145
|
+
return true
|
146
|
+
end
|
147
|
+
|
148
|
+
def Resque.kill_workers(count=nil)
|
149
|
+
pids = Resque.workers.map{|w| w.to_s.split(":").second}
|
150
|
+
if count.to_i > pids.length or count == 0
|
151
|
+
return false
|
152
|
+
elsif count
|
153
|
+
"kill #{pids[0..count-1].join(" ")}".bash(false)
|
154
|
+
elsif pids.length>0
|
155
|
+
"kill #{pids.join(" ")}".bash(false)
|
156
|
+
else
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
return true
|
160
|
+
end
|
161
|
+
|
162
|
+
def Resque.prep_workers(max_workers=Resque.config['max_workers'])
|
163
|
+
curr_workers = Resque.workers.length
|
164
|
+
if curr_workers > max_workers
|
165
|
+
#kill as many idlers as necessary
|
166
|
+
Resque.kill_idle_workers(curr_workers - max_workers)
|
167
|
+
#wait a few secs for these guys to die
|
168
|
+
sleep 10
|
169
|
+
curr_workers = Resque.workers.length
|
170
|
+
if curr_workers > max_workers
|
171
|
+
#kill working workers
|
172
|
+
Resque.kill_workers(curr_workers - max_workers)
|
173
|
+
end
|
174
|
+
else
|
175
|
+
Resque.start_workers(max_workers-curr_workers)
|
176
|
+
end
|
177
|
+
return true
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class String
|
2
|
+
def to_a
|
3
|
+
return [self]
|
4
|
+
end
|
5
|
+
def oputs
|
6
|
+
STDOUT.puts self
|
7
|
+
end
|
8
|
+
def eputs
|
9
|
+
STDERR.puts self
|
10
|
+
end
|
11
|
+
def opp
|
12
|
+
pp self
|
13
|
+
end
|
14
|
+
def bash(except=true)
|
15
|
+
pid,stdin,stdout,stderr = Open4.popen4(self)
|
16
|
+
raise stderr.read if (stderr.read.length>0 and except==true)
|
17
|
+
return stdout.read
|
18
|
+
end
|
19
|
+
def googlesafe
|
20
|
+
v=self
|
21
|
+
return "" if v.to_s==""
|
22
|
+
#normalize numbers by removing '$', '%', ',', ' '
|
23
|
+
vnorm = v.to_s.norm_num
|
24
|
+
vdigits = vnorm.split(".").last.length
|
25
|
+
if vnorm.to_f.to_s=="Infinity"
|
26
|
+
#do nothing
|
27
|
+
elsif ("%.#{vdigits}f" % vnorm.to_f.to_s)==vnorm
|
28
|
+
#round floats to 5 sig figs
|
29
|
+
v=vnorm.to_f.round(5)
|
30
|
+
elsif vnorm.to_i.to_s==vnorm
|
31
|
+
#make sure integers are cast as such
|
32
|
+
v=vnorm.to_i
|
33
|
+
elsif v.is_time?
|
34
|
+
begin
|
35
|
+
time_vs = v.split("/")
|
36
|
+
if time_vs.first.length<=2 and time_vs.second.length<=2
|
37
|
+
#date is of the form mm/dd/yyyy or mm/dd/yy
|
38
|
+
v=Time.parse("#{time_vs[2][0..3]}/#{time_vs[0]}/#{time_vs[1]}#{time_vs[2][4..-1]}")
|
39
|
+
else
|
40
|
+
v=Time.parse(v)
|
41
|
+
end
|
42
|
+
rescue
|
43
|
+
#do nothing
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return v
|
47
|
+
end
|
48
|
+
def norm_num
|
49
|
+
return self.gsub(",","").gsub("$","").gsub("%","").gsub(" ","")
|
50
|
+
end
|
51
|
+
def is_float?
|
52
|
+
return self.norm_num.to_f.to_s == self.norm_num.to_s
|
53
|
+
end
|
54
|
+
def is_fixnum?
|
55
|
+
return self.norm_num.to_i.to_s == self.norm_num.to_s
|
56
|
+
end
|
57
|
+
def is_time?
|
58
|
+
if ((self.count("-")==2 or self.count("/")==2) and self.length>=8 and self.length<=20)
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
split_str = self.split(" ")
|
62
|
+
if split_str.length==3 and
|
63
|
+
split_str.first.count("-")==2 and
|
64
|
+
split_str.last.first=="-" and
|
65
|
+
split_str.second.count(":")==2
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def json_to_hash
|
70
|
+
begin
|
71
|
+
return JSON.parse(self)
|
72
|
+
rescue => exc
|
73
|
+
return {}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
def tsv_to_hash_array
|
77
|
+
rows = self.split("\n")
|
78
|
+
return [] if rows.first.nil?
|
79
|
+
return [{rows.first=>nil}] if (rows.length==2 and rows.second==nil)
|
80
|
+
headers = rows.first.split("\t")
|
81
|
+
if rows.length==1
|
82
|
+
#return single member hash with all nil values
|
83
|
+
return [headers.map{|k| {k=>nil}}.inject{|k,h| h.merge(k)}]
|
84
|
+
end
|
85
|
+
row_hash_arr =[]
|
86
|
+
rows[1..-1].each do |row|
|
87
|
+
cols = row.split("\t")
|
88
|
+
row_hash = {}
|
89
|
+
headers.each_with_index{|h,h_i| row_hash[h] = cols[h_i]}
|
90
|
+
row_hash_arr << row_hash
|
91
|
+
end
|
92
|
+
return row_hash_arr
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Mobilize
|
2
|
+
require 'action_mailer'
|
3
|
+
class Emailer < ActionMailer::Base
|
4
|
+
ActionMailer::Base.delivery_method = :smtp
|
5
|
+
|
6
|
+
ActionMailer::Base.smtp_settings = {
|
7
|
+
:address => "smtp.gmail.com",
|
8
|
+
:port => 587,
|
9
|
+
:domain => 'ngmoco.com',
|
10
|
+
:user_name => Gdriver.owner_email,
|
11
|
+
:password => Gdriver.password(Gdriver.owner_email),
|
12
|
+
:authentication => 'plain',
|
13
|
+
:enable_starttls_auto => true }
|
14
|
+
|
15
|
+
def write(subj,
|
16
|
+
bod="",
|
17
|
+
recipient=Jobtracker.admin_emails.join(","))
|
18
|
+
mail(:from=>Gdriver.owner_email,
|
19
|
+
:to=>recipient,
|
20
|
+
:subject=>subj,
|
21
|
+
:body=>bod)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|