mobilize-base 1.0.0
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/.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
|