rq-ruby1.8 3.4.3

Sign up to get free protection for your applications and to get access to all the features.
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 %>