rq-ruby1.8 3.4.3

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.
Files changed (67) hide show
  1. data/Gemfile +22 -0
  2. data/Gemfile.lock +22 -0
  3. data/INSTALL +166 -0
  4. data/LICENSE +10 -0
  5. data/Makefile +6 -0
  6. data/README +1183 -0
  7. data/Rakefile +37 -0
  8. data/TODO +24 -0
  9. data/TUTORIAL +230 -0
  10. data/VERSION +1 -0
  11. data/bin/rq +902 -0
  12. data/bin/rqmailer +865 -0
  13. data/example/a.rb +7 -0
  14. data/extconf.rb +198 -0
  15. data/gemspec.rb +40 -0
  16. data/install.rb +210 -0
  17. data/lib/rq.rb +155 -0
  18. data/lib/rq/arrayfields.rb +371 -0
  19. data/lib/rq/backer.rb +31 -0
  20. data/lib/rq/configfile.rb +82 -0
  21. data/lib/rq/configurator.rb +40 -0
  22. data/lib/rq/creator.rb +54 -0
  23. data/lib/rq/cron.rb +144 -0
  24. data/lib/rq/defaultconfig.txt +5 -0
  25. data/lib/rq/deleter.rb +51 -0
  26. data/lib/rq/executor.rb +40 -0
  27. data/lib/rq/feeder.rb +527 -0
  28. data/lib/rq/ioviewer.rb +48 -0
  29. data/lib/rq/job.rb +51 -0
  30. data/lib/rq/jobqueue.rb +947 -0
  31. data/lib/rq/jobrunner.rb +110 -0
  32. data/lib/rq/jobrunnerdaemon.rb +193 -0
  33. data/lib/rq/lister.rb +47 -0
  34. data/lib/rq/locker.rb +43 -0
  35. data/lib/rq/lockfile.rb +564 -0
  36. data/lib/rq/logging.rb +124 -0
  37. data/lib/rq/mainhelper.rb +189 -0
  38. data/lib/rq/orderedautohash.rb +39 -0
  39. data/lib/rq/orderedhash.rb +240 -0
  40. data/lib/rq/qdb.rb +733 -0
  41. data/lib/rq/querier.rb +98 -0
  42. data/lib/rq/rails.rb +80 -0
  43. data/lib/rq/recoverer.rb +28 -0
  44. data/lib/rq/refresher.rb +80 -0
  45. data/lib/rq/relayer.rb +283 -0
  46. data/lib/rq/resource.rb +22 -0
  47. data/lib/rq/resourcemanager.rb +40 -0
  48. data/lib/rq/resubmitter.rb +100 -0
  49. data/lib/rq/rotater.rb +98 -0
  50. data/lib/rq/sleepcycle.rb +46 -0
  51. data/lib/rq/snapshotter.rb +40 -0
  52. data/lib/rq/sqlite.rb +286 -0
  53. data/lib/rq/statuslister.rb +48 -0
  54. data/lib/rq/submitter.rb +113 -0
  55. data/lib/rq/toucher.rb +182 -0
  56. data/lib/rq/updater.rb +94 -0
  57. data/lib/rq/usage.rb +1222 -0
  58. data/lib/rq/util.rb +304 -0
  59. data/rdoc.sh +17 -0
  60. data/rq-ruby1.8.gemspec +120 -0
  61. data/test/.gitignore +1 -0
  62. data/test/test_rq.rb +145 -0
  63. data/white_box/crontab +2 -0
  64. data/white_box/joblist +8 -0
  65. data/white_box/killrq +18 -0
  66. data/white_box/rq_killer +27 -0
  67. metadata +208 -0
@@ -0,0 +1,865 @@
1
+ #! /opt/local/bin/ruby
2
+
3
+ ### built-in
4
+ require 'openssl'
5
+ require 'net/smtp'
6
+ require 'io/wait'
7
+ require 'erb'
8
+ require 'socket'
9
+
10
+ ### rubyforge
11
+ require 'rubygems'
12
+ require 'main'
13
+ require 'open4'
14
+ #require 'mailfactory' # patched version inlined below
15
+
16
+ Main {
17
+ option('config', 'c'){
18
+ argument_required
19
+ }
20
+
21
+ option('template', 't'){
22
+ argument_required
23
+ }
24
+
25
+ option('belch', 'b'){
26
+ }
27
+
28
+ option('send', 's'){
29
+ }
30
+
31
+ option('queue', 'q'){
32
+ argument_required
33
+ }
34
+
35
+ def run #--{{{
36
+ indicating_that_rqmailer_failed do
37
+ setup
38
+ load_config
39
+ load_template
40
+ generate_cmd
41
+ run_cmd
42
+ expand_template
43
+ mail_message
44
+ end
45
+ exit @exit_status || 42
46
+ end #--}}}
47
+
48
+ def setup #--{{{
49
+ @config = {}
50
+ @rq = ENV['RQ']
51
+ @rq_data = ENV['RQ_DATA']
52
+ if(@rq.nil? and @rq_data.nil? and argv.first and test(?d, argv.first))
53
+ @rq_data = File.expand_path argv.shift
54
+ @rq = true
55
+ end
56
+ @exit_status = 0
57
+ @exception = nil
58
+ @template_expander = TemplateExpander.instance
59
+ @stdin = @stdout = @stderr = nil
60
+ @belch = params['belch'].given?
61
+ @send = params['send'].given?
62
+ @hostname = Socket.gethostname rescue 'localhost.localdomain'
63
+ @mail = MailFactory.new
64
+ end #--}}}
65
+
66
+ def load_config #--{{{
67
+ if param['config'].given?
68
+ @config = YAML.load IO.read(param['config'].value)
69
+ else
70
+ if @rq_data
71
+ config = File.join @rq_data, 'config'
72
+ @config = YAML.load IO.read(config)
73
+ else
74
+ abort 'no config'
75
+ end
76
+ end
77
+ end #--}}}
78
+
79
+ def load_template #--{{{
80
+ if param['template'].given?
81
+ @template = IO.read param['template'].value
82
+ else
83
+ if @rq_data
84
+ template = File.join @rq_data, 'template'
85
+ @template = IO.read(template)
86
+ else
87
+ @template = DATA.read
88
+ end
89
+ end
90
+ end #--}}}
91
+
92
+ def generate_cmd #--{{{
93
+ stdin = argv.first == '-' && argv.delete('-')
94
+ @cmd =
95
+ if stdin
96
+ STDIN.read
97
+ else
98
+ @config['command'] || @config['cmd'] || argv.join(' ')
99
+ end
100
+ abort 'no cmd' if @cmd.to_s.strip.empty?
101
+ end #--}}}
102
+
103
+ def run_cmd #--{{{
104
+ if @rq
105
+ @stdin = STDIN if STDIN.ready?
106
+ @stdout = STDOUT
107
+ @stderr = STDERR
108
+ STDOUT.flush; STDERR.flush
109
+ @exit_status = Open4.spawn @cmd, 0=>@stdin, 1=>@stdout, 2=>@stderr
110
+ STDOUT.flush; STDERR.flush
111
+ else
112
+ @stdin = STDIN if STDIN.ready?
113
+ @stdin = IO.read(@stdin) if @stdin
114
+ @stdout = ''
115
+ @stderr = ''
116
+ @exit_status = Open4.spawn @cmd, 0=>@stdin, 1=>@stdout, 2=>@stderr
117
+ end
118
+ update_config_with_cmd_info
119
+ end #--}}}
120
+
121
+ def errmsg e #--{{{
122
+ m, c, bt = e.message, e.class, (e.backtrace || []).join("\n")
123
+ "#{ m } (#{ c })\n#{ bt }"
124
+ end #--}}}
125
+
126
+ def indicating_that_rqmailer_failed #--{{{
127
+ begin
128
+ yield
129
+ rescue Exception => e
130
+ @exception = e
131
+ STDERR.puts errmsg(e)
132
+ @exit_status = 42
133
+ end
134
+ end #--}}}
135
+
136
+ def update_config_with_cmd_info #--{{{
137
+ if @rq
138
+ %w( stdin stdout stderr ).each do |iof|
139
+ path = instance_variable_get "@rq_#{ iof }"
140
+ buf = IO.read path rescue ''
141
+ @config[iof] = buf
142
+ @config["#{ iof }_path"] = path
143
+ end
144
+ else
145
+ @config['stdin'] = @stdin
146
+ @config['stdin_path'] = '-'
147
+ @config['stdout'] = @stdout
148
+ @config['stdout_path'] = '-'
149
+ @config['stderr'] = @stderr
150
+ @config['stderr_path'] = '-'
151
+ end
152
+
153
+ @config['cmd'] = @config['command'] = @cmd
154
+ @config['exit_status'] = @config['exitstatus'] = @exit_status
155
+ end #--}}}
156
+
157
+ def expand_template #--{{{
158
+ @template_expander.configure @config
159
+ @template_expander.configure 'mail' => @mail
160
+ mconfig = @config['mail'] || {}
161
+ @template_expander.configure mconfig
162
+ @message = @template_expander.expand @template
163
+ end #--}}}
164
+
165
+ def mail_message #--{{{
166
+ build_mail!
167
+
168
+ deliver = @config.has_key?('smtp') ? 'smtp' : 'sendmail'
169
+
170
+ if deliver == 'smtp'
171
+ hash = @config['smtp'] || {}
172
+ server = hash['server']
173
+ port = Integer hash['port']
174
+ hostname = hash['hostname'] || @hostname || 'localhost.localdomain'
175
+ account = hash['account']
176
+ password = hash['password']
177
+ authtype = hash['authtype'] || :plain
178
+ tls = hash['tls'] ? true : false
179
+ else
180
+ sendmail = @config['sendmail'] || '/usr/sbin/sendmail -i -t'
181
+ end
182
+
183
+ if @belch
184
+ if @send
185
+ case deliver
186
+ when 'smtp'
187
+ smtp = Net::SMTP.new server, port
188
+ smtp.set_debug_output STDERR
189
+ smtp.start hostname, account, password, authtype, tls do |connection|
190
+ connection.send_message @mail, account, @mail.recipients
191
+ end
192
+ when 'sendmail'
193
+ IO.popen(sendmail, 'w+') do |pipe|
194
+ pipe.write @mail.to_s.gsub(%r/\r/,'')
195
+ pipe.flush
196
+ end
197
+ end
198
+ end
199
+ STDOUT.puts @mail
200
+ else
201
+ case deliver
202
+ when 'smtp'
203
+ debug_output = open File.join(@rq_data, 'smtp'), 'w'
204
+ at_exit{ debug_output.close rescue nil }
205
+ smtp = Net::SMTP.new server, port
206
+ smtp.set_debug_output debug_output
207
+ smtp.start hostname, account, password, authtype, tls do |connection|
208
+ connection.send_message @mail, account, @mail.recipients
209
+ end
210
+ when 'sendmail'
211
+ IO.popen(sendmail, 'w+') do |pipe|
212
+ pipe.write @mail.to_s.gsub(%r/\r/,'')
213
+ pipe.flush
214
+ end
215
+ end
216
+ open(File.join(@rq_data, 'mail'), 'w'){|fd| fd.write @mail} rescue nil
217
+ STDERR.puts @mail
218
+ end
219
+
220
+ end #--}}}
221
+
222
+ def build_mail! #--{{{
223
+ mconfig = @config['mail'] || @config
224
+
225
+ attributes = %w(
226
+ to
227
+ from
228
+ cc
229
+ bcc
230
+ subject
231
+ )
232
+ attributes.each do |attribute|
233
+ up, down = attribute.downcase.capitalize, attribute.downcase
234
+ [up, down].each do |attribute|
235
+ if mconfig.has_key?(attribute)
236
+ value = [mconfig[attribute]].flatten.join ','
237
+ @mail.send "#{ up }=", value
238
+ @mail.send "#{ down }=", value
239
+ end
240
+ end
241
+ end
242
+
243
+ methods = %w(
244
+ attach
245
+ attachment
246
+ attachments
247
+ Attach
248
+ Attachment
249
+ Attachments
250
+ )
251
+ methods.each do |method|
252
+ if mconfig.has_key?(method)
253
+ values = [mconfig[method]].flatten
254
+ values.each do |value|
255
+ @mail.attach value
256
+ end
257
+ end
258
+ end
259
+
260
+ class << @mail
261
+ def recipients
262
+ to.to_s.split %r/,/
263
+ end
264
+ end
265
+
266
+ @mail.subject ||= "rqmailer"
267
+ @mail.text = @message
268
+ @mail
269
+ end #--}}}
270
+
271
+ class TemplateExpander #--{{{
272
+ alias_method "__instance_variable_set__", "instance_variable_set"
273
+ alias_method "__instance_eval__", "instance_eval"
274
+ instance_methods.each{|m| undef_method unless m[%r/__/]}
275
+ alias_method "instance_eval", "__instance_eval__"
276
+ alias_method "instance_variable_set", "__instance_variable_set__"
277
+
278
+ def self.new *a, &b
279
+ @instance ||= super
280
+ end
281
+ def self.instance *a, &b
282
+ new *a, &b
283
+ end
284
+ def self.const_missing c
285
+ msg = c.to_s.downcase
286
+ if @instance.respond_to? msg
287
+ @instance.send msg
288
+ else
289
+ super
290
+ end
291
+ end
292
+ def singleton_class &b #--{{{
293
+ @sc ||=
294
+ class << self
295
+ self
296
+ end
297
+ b ? @sc.module_eval(&b) : @sc
298
+ end #--}}}
299
+ def set key, value #--{{{
300
+ key = key.to_s.downcase
301
+ instance_variable_set "@#{ key }", value
302
+ singleton_class do
303
+ define_method(key){ instance_variable_get "@#{ key }" }
304
+ define_method(key.capitalize){ instance_variable_get "@#{ key }" }
305
+ end
306
+ end #--}}}
307
+ def configure hash #--{{{
308
+ hash.each{|k,v| set k, v}
309
+ end #--}}}
310
+ def expand message #--{{{
311
+ ERB.new(message).result binding
312
+ end #--}}}
313
+ def lazy #--{{{
314
+ singleton_class{ define_method m, &b }
315
+ end #--}}}
316
+ def method_missing m, *a, &b
317
+ super unless defined? @mail
318
+ @mail.send m, *a, &b
319
+ end
320
+ end #--}}}
321
+ }
322
+
323
+
324
+ BEGIN {
325
+
326
+ ### hacking in smtp tls support
327
+ #--{{{
328
+ # Original code believed public domain from ruby-talk or ruby-core email.
329
+ # Modifications by Kyle Maxwell <kyle@kylemaxwell.com> used under MIT license.
330
+
331
+ require "openssl"
332
+ require "net/smtp"
333
+
334
+ # :stopdoc:
335
+
336
+ class Net::SMTP
337
+
338
+ class << self
339
+ send :remove_method, :start
340
+ end
341
+
342
+ def self.start( address, port = nil,
343
+ helo = 'localhost.localdomain',
344
+ user = nil, secret = nil, authtype = nil, use_tls = false,
345
+ &block) # :yield: smtp
346
+ new(address, port).start(helo, user, secret, authtype, use_tls, &block)
347
+ end
348
+
349
+ alias tls_old_start start
350
+
351
+ def start( helo = 'localhost.localdomain',
352
+ user = nil, secret = nil, authtype = nil, use_tls = false ) # :yield: smtp
353
+ start_method = use_tls ? :do_tls_start : :do_start
354
+ if block_given?
355
+ begin
356
+ send start_method, helo, user, secret, authtype
357
+ return yield(self)
358
+ ensure
359
+ do_finish
360
+ end
361
+ else
362
+ send start_method, helo, user, secret, authtype
363
+ return self
364
+ end
365
+ end
366
+
367
+ private
368
+
369
+ def do_tls_start(helodomain, user, secret, authtype)
370
+ raise IOError, 'SMTP session already started' if @started
371
+ check_auth_args user, secret, authtype if user or secret
372
+
373
+ sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
374
+ @socket = Net::InternetMessageIO.new(sock)
375
+ @socket.read_timeout = 60 #@read_timeout
376
+ @socket.debug_output = @debug_output
377
+
378
+ check_response(critical { recv_response() })
379
+ do_helo(helodomain)
380
+
381
+ raise 'openssl library not installed' unless defined?(OpenSSL)
382
+ starttls
383
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
384
+ ssl.sync_close = true
385
+ ssl.connect
386
+ @socket = Net::InternetMessageIO.new(ssl)
387
+ @socket.read_timeout = 60 #@read_timeout
388
+ @socket.debug_output = @debug_output
389
+ do_helo(helodomain)
390
+
391
+ authenticate user, secret, authtype if user
392
+ @started = true
393
+ ensure
394
+ unless @started
395
+ # authentication failed, cancel connection.
396
+ @socket.close if not @started and @socket and not @socket.closed?
397
+ @socket = nil
398
+ end
399
+ end
400
+
401
+ def do_helo(helodomain)
402
+ begin
403
+ if @esmtp
404
+ ehlo helodomain
405
+ else
406
+ helo helodomain
407
+ end
408
+ rescue Net::ProtocolError
409
+ if @esmtp
410
+ @esmtp = false
411
+ @error_occured = false
412
+ retry
413
+ end
414
+ raise
415
+ end
416
+ end
417
+
418
+ def starttls
419
+ getok('STARTTLS')
420
+ end
421
+
422
+ alias tls_old_quit quit
423
+
424
+ def quit
425
+ begin
426
+ getok('QUIT')
427
+ rescue EOFError
428
+ end
429
+ end
430
+
431
+ end unless Net::SMTP.private_method_defined? :do_tls_start or
432
+ Net::SMTP.method_defined? :tls?
433
+ #--}}}
434
+
435
+ ### inlining mailfactory
436
+ #--{{{
437
+ # = Overview:
438
+ # A simple to use module for generating RFC compliant MIME mail
439
+ # ---
440
+ # = License:
441
+ # Author:: David Powers
442
+ # Copyright:: May, 2005
443
+ # License:: Ruby License
444
+ # ---
445
+ # = Usage:
446
+ # require 'net/smtp'
447
+ # require 'rubygems'
448
+ # require 'mailfactory'
449
+ #
450
+ #
451
+ # mail = MailFactory.new()
452
+ # mail.to = "test@test.com"
453
+ # mail.from = "sender@sender.com"
454
+ # mail.subject = "Here are some files for you!"
455
+ # mail.text = "This is what people with plain text mail readers will see"
456
+ # mail.html = "A little something <b>special</b> for people with HTML readers"
457
+ # mail.attach("/etc/fstab")
458
+ # mail.attach("/some/other/file")
459
+ #
460
+ # Net::SMTP.start('smtp1.testmailer.com', 25, 'mail.from.domain', fromaddress, password, :cram_md5) { |smtp|
461
+ # mail.to = toaddress
462
+ # smtp.send_message(mail.to_s(), fromaddress, toaddress)
463
+ # }
464
+
465
+
466
+
467
+ require 'base64'
468
+ require 'pathname'
469
+
470
+ # try to bring in the mime/types module, make a dummy module if it can't be found
471
+ begin
472
+ begin
473
+ require 'rubygems'
474
+ rescue LoadError
475
+ end
476
+ require 'mime/types'
477
+ rescue LoadError
478
+ module MIME
479
+ class Types
480
+ def Types::type_for(filename)
481
+ return('')
482
+ end
483
+ end
484
+ end
485
+ end
486
+
487
+ # An easy class for creating a mail message
488
+ class MailFactory
489
+
490
+ def initialize()
491
+ @headers = Array.new()
492
+ @attachments = Array.new()
493
+ @attachmentboundary = generate_boundary()
494
+ @bodyboundary = generate_boundary()
495
+ @html = nil
496
+ @text = nil
497
+ end
498
+
499
+
500
+ # adds a header to the bottom of the headers
501
+ def add_header(header, value)
502
+ @headers << "#{header}: #{value}"
503
+ end
504
+
505
+
506
+ # removes the named header - case insensitive
507
+ def remove_header(header)
508
+ @headers.each_index() { |i|
509
+ if(@headers[i] =~ /^#{Regexp.escape(header)}:/i)
510
+ @headers.delete_at(i)
511
+ end
512
+ }
513
+ end
514
+
515
+
516
+ # sets a header (removing any other versions of that header)
517
+ def set_header(header, value)
518
+ remove_header(header)
519
+ add_header(header, value)
520
+ end
521
+
522
+
523
+ def replyto=(newreplyto)
524
+ remove_header("Reply-To")
525
+ add_header("Reply-To", newreplyto)
526
+ end
527
+
528
+
529
+ def replyto()
530
+ return(get_header("Reply-To")[0])
531
+ end
532
+
533
+ def self.hattr h
534
+ h = h.to_s
535
+ module_eval <<-code
536
+ def #{ h.downcase }=(value)
537
+ remove_header("#{ h.downcase.capitalize }")
538
+ add_header("#{ h.downcase.capitalize }", value)
539
+ end
540
+ def #{ h.downcase }()
541
+ return(get_header("#{ h.downcase.capitalize }")[0])
542
+ end
543
+ code
544
+ end
545
+ %w( to from cc bcc subject ).each{|h| hattr h}
546
+
547
+
548
+ # sets the plain text body of the message
549
+ def text=(newtext)
550
+ @text = newtext
551
+ end
552
+
553
+
554
+ # sets the HTML body of the message. Only the body of the
555
+ # html should be provided
556
+ def html=(newhtml)
557
+ @html = "<html>\n<head>\n<meta content=\"text/html;charset=ISO-8859-1\" http-equiv=\"Content-Type\">\n</head>\n<body bgcolor=\"#ffffff\" text=\"#000000\">\n#{newhtml}\n</body>\n</html>"
558
+ end
559
+
560
+
561
+ # sets the HTML body of the message. The entire HTML section should be provided
562
+ def rawhtml=(newhtml)
563
+ @html = newhtml
564
+ end
565
+
566
+
567
+ # implement method missing to provide helper methods for setting and getting headers.
568
+ # Headers with '-' characters may be set/gotten as 'x_mailer' or 'XMailer' (splitting
569
+ # will occur between capital letters or on '_' chracters)
570
+ def method_missing(methId, *args)
571
+ name = methId.id2name()
572
+
573
+ # mangle the name if we have to
574
+ if(name =~ /_/)
575
+ name = name.gsub(/_/, '-')
576
+ elsif(name =~ /[A-Z]/)
577
+ name = name.gsub(/([a-zA-Z])([A-Z])/, '\1-\2')
578
+ end
579
+
580
+ # determine if it sets or gets, and do the right thing
581
+ if(name =~ /=$/)
582
+ if(args.length != 1)
583
+ super(methId, args)
584
+ end
585
+ set_header(name[/^(.*)=$/, 1], args[0])
586
+ else
587
+ if(args.length != 0)
588
+ super(methId, args)
589
+ end
590
+ headers = get_header(name)
591
+ return(get_header(name))
592
+ end
593
+ end
594
+
595
+
596
+ # returns the value (or values) of the named header in an array
597
+ def get_header(header)
598
+ headers = Array.new()
599
+ headerregex = /^#{Regexp.escape(header)}:/i
600
+ @headers.each() { |h|
601
+ if(headerregex.match(h))
602
+ headers << h[/^[^:]+:(.*)/i, 1].strip()
603
+ end
604
+ }
605
+
606
+ return(headers)
607
+ end
608
+
609
+
610
+ # returns true if the email is multipart
611
+ def multipart?()
612
+ if(@attachments.length > 0 or @html != nil)
613
+ return(true)
614
+ else
615
+ return(false)
616
+ end
617
+ end
618
+
619
+
620
+ # builds an email and returns it as a string. Takes the following options:
621
+ # <tt>:messageid</tt>:: Adds a message id to the message based on the from header (defaults to false)
622
+ # <tt>:date</tt>:: Adds a date to the message if one is not present (defaults to true)
623
+ def construct(options = Hash.new)
624
+ if(options[:date] == nil)
625
+ options[:date] = true
626
+ end
627
+
628
+ if(options[:messageid])
629
+ # add a unique message-id
630
+ remove_header("Message-ID")
631
+ sendingdomain = get_header('from')[0].to_s()[/@([-a-zA-Z0-9._]+)/,1].to_s()
632
+ add_header("Message-ID", "<#{Time.now.to_f()}.#{Process.euid()}.#{String.new.object_id()}@#{sendingdomain}>")
633
+ end
634
+
635
+ if(options[:date])
636
+ if(get_header("Date").length == 0)
637
+ add_header("Date", Time.now.strftime("%a, %d %b %Y %H:%M:%S %z"))
638
+ end
639
+ end
640
+
641
+ # Add a mime header if we don't already have one and we have multiple parts
642
+ if(multipart?())
643
+ if(get_header("MIME-Version").length == 0)
644
+ add_header("MIME-Version", "1.0")
645
+ end
646
+
647
+ if(get_header("Content-Type").length == 0)
648
+ if(@attachments.length == 0)
649
+ add_header("Content-Type", "multipart/alternative;boundary=\"#{@bodyboundary}\"")
650
+ else
651
+ add_header("Content-Type", "multipart/mixed; boundary=\"#{@attachmentboundary}\"")
652
+ end
653
+ end
654
+ end
655
+
656
+ return("#{headers_to_s()}#{body_to_s()}")
657
+ end
658
+
659
+
660
+ # returns a formatted email - equivalent to construct(:messageid => true)
661
+ def to_s()
662
+ return(construct(:messageid => true))
663
+ end
664
+
665
+
666
+ # generates a unique boundary string
667
+ def generate_boundary()
668
+ randomstring = Array.new()
669
+ 1.upto(25) {
670
+ whichglyph = rand(100)
671
+ if(whichglyph < 40)
672
+ randomstring << (rand(25) + 65).chr()
673
+ elsif(whichglyph < 70)
674
+ randomstring << (rand(25) + 97).chr()
675
+ elsif(whichglyph < 90)
676
+ randomstring << (rand(10) + 48).chr()
677
+ elsif(whichglyph < 95)
678
+ randomstring << '.'
679
+ else
680
+ randomstring << '_'
681
+ end
682
+ }
683
+ return("----=_NextPart_#{randomstring.join()}")
684
+ end
685
+
686
+
687
+ # adds an attachment to the mail. Type may be given as a mime type. If it
688
+ # is left off and the MIME::Types module is available it will be determined automagically.
689
+ # If the optional attachemntheaders is given, then they will be added to the attachment
690
+ # boundary in the email, which can be used to produce Content-ID markers. attachmentheaders
691
+ # can be given as an Array or a String.
692
+ def add_attachment(filename, type=nil, attachmentheaders = nil)
693
+ attachment = Hash.new()
694
+ attachment['filename'] = Pathname.new(filename).basename
695
+ if(type == nil)
696
+ attachment['mimetype'] = MIME::Types.type_for(filename).to_s
697
+ else
698
+ attachment['mimetype'] = type
699
+ end
700
+
701
+ # Open in rb mode to handle Windows, which mangles binary files opened in a text mode
702
+ File.open(filename, "rb") { |fp|
703
+ attachment['attachment'] = file_encode(fp.read())
704
+ }
705
+
706
+ if(attachmentheaders != nil)
707
+ if(!attachmentheaders.kind_of?(Array))
708
+ attachmentheaders = attachmentheaders.split(/\r?\n/)
709
+ end
710
+ attachment['headers'] = attachmentheaders
711
+ end
712
+
713
+ @attachments << attachment
714
+ end
715
+
716
+
717
+ # adds an attachment to the mail as emailfilename. Type may be given as a mime type. If it
718
+ # is left off and the MIME::Types module is available it will be determined automagically.
719
+ # file may be given as an IO stream (which will be read until the end) or as a filename.
720
+ # If the optional attachemntheaders is given, then they will be added to the attachment
721
+ # boundary in the email, which can be used to produce Content-ID markers. attachmentheaders
722
+ # can be given as an Array of a String.
723
+ def add_attachment_as(file, emailfilename, type=nil, attachmentheaders = nil)
724
+ attachment = Hash.new()
725
+ attachment['filename'] = emailfilename
726
+
727
+ if(type != nil)
728
+ attachment['mimetype'] = type.to_s()
729
+ elsif(file.kind_of?(String) or file.kind_of?(Pathname))
730
+ attachment['mimetype'] = MIME::Types.type_for(file.to_s()).to_s
731
+ else
732
+ attachment['mimetype'] = ''
733
+ end
734
+
735
+ if(file.kind_of?(String) or file.kind_of?(Pathname))
736
+ # Open in rb mode to handle Windows, which mangles binary files opened in a text mode
737
+ File.open(file.to_s(), "rb") { |fp|
738
+ attachment['attachment'] = file_encode(fp.read())
739
+ }
740
+ elsif(file.respond_to?(:read))
741
+ attachment['attachment'] = file_encode(file.read())
742
+ else
743
+ raise(Exception, "file is not a supported type (must be a String, Pathnamem, or support read method)")
744
+ end
745
+
746
+ if(attachmentheaders != nil)
747
+ if(!attachmentheaders.kind_of?(Array))
748
+ attachmentheaders = attachmentheaders.split(/\r?\n/)
749
+ end
750
+ attachment['headers'] = attachmentheaders
751
+ end
752
+
753
+ @attachments << attachment
754
+ end
755
+
756
+
757
+ alias attach add_attachment
758
+ alias attach_as add_attachment_as
759
+
760
+ protected
761
+
762
+ # returns the @headers as a properly formatted string
763
+ def headers_to_s()
764
+ return("#{@headers.join("\r\n")}\r\n\r\n")
765
+ end
766
+
767
+
768
+ # returns the body as a properly formatted string
769
+ def body_to_s()
770
+ body = Array.new()
771
+
772
+ # simple message with one part
773
+ if(!multipart?())
774
+ return(@text)
775
+ else
776
+ body << "This is a multi-part message in MIME format.\r\n\r\n--#{@attachmentboundary}\r\nContent-Type: multipart/alternative; boundary=\"#{@bodyboundary}\""
777
+
778
+ if(@attachments.length > 0)
779
+ # text part
780
+ body << "#{buildbodyboundary('text/plain; charset=ISO-8859-1; format=flowed', '7bit')}\r\n\r\n#{@text}"
781
+
782
+ # html part if one is provided
783
+ if @html
784
+ body << "#{buildbodyboundary('text/html; charset=ISO-8859-1', '7bit')}\r\n\r\n#{@html}"
785
+ end
786
+
787
+ body << "--#{@bodyboundary}--"
788
+
789
+ # and, the attachments
790
+ if(@attachments.length > 0)
791
+ @attachments.each() { |attachment|
792
+ body << "#{buildattachmentboundary(attachment)}\r\n\r\n#{attachment['attachment']}"
793
+ }
794
+ body << "\r\n--#{@attachmentboundary}--"
795
+ end
796
+ else
797
+ # text part
798
+ body << "#{buildbodyboundary('text/plain; charset=ISO-8859-1; format=flowed', '7bit')}\r\n\r\n#{@text}"
799
+
800
+ # html part
801
+ body << "#{buildbodyboundary('text/html; charset=ISO-8859-1', '7bit')}\r\n\r\n#{@html}"
802
+
803
+ body << "--#{@bodyboundary}--"
804
+ end
805
+
806
+ return(body.join("\r\n\r\n"))
807
+ end
808
+ end
809
+
810
+
811
+ # builds a boundary string for including attachments in the body, expects an attachment hash as built by
812
+ # add_attachment and add_attachment_as
813
+ def buildattachmentboundary(attachment)
814
+ disposition = "Content-Disposition: inline; filename=\"#{attachment['filename']}\""
815
+ boundary = "--#{@attachmentboundary}\r\nContent-Type: #{attachment['mimetype']}; name=\"#{attachment['filename']}\"\r\nContent-Transfer-Encoding: base64\r\n#{disposition}"
816
+ if(attachment['headers'])
817
+ boundary = boundary + "\r\n#{attachment['headers'].join("\r\n")}"
818
+ end
819
+
820
+ return(boundary)
821
+ end
822
+
823
+
824
+ # builds a boundary string for inclusion in the body of a message
825
+ def buildbodyboundary(type, encoding)
826
+ return("--#{@bodyboundary}\r\nContent-Type: #{type}\r\nContent-Transfer-Encoding: #{encoding}")
827
+ end
828
+
829
+
830
+ # returns a base64 encoded version of the contents of str
831
+ def file_encode(str)
832
+ collection = Array.new()
833
+ enc = Base64.encode64(str)
834
+ # while(enc.length > 60)
835
+ # collection << enc.slice!(0..59)
836
+ # end
837
+ # collection << enc
838
+ # return(collection.join("\n"))
839
+ return(enc)
840
+ end
841
+
842
+ end
843
+
844
+ #--}}}
845
+
846
+ }
847
+
848
+
849
+ __END__
850
+ ### rqmailer
851
+
852
+ CMD:
853
+ <%= cmd %>
854
+
855
+ EXIT_STATUS:
856
+ <%= exit_status %>
857
+
858
+ STDIN:
859
+ <%= stdin %>
860
+
861
+ STDOUT:
862
+ <%= stdout %>
863
+
864
+ STDERR:
865
+ <%= stderr %>