]> git.cworth.org Git - sup/blobdiff - lib/sup/util.rb
attachment and message mime stuff cleanups in preparation for fowards as attachments
[sup] / lib / sup / util.rb
index e13aac4abb952537fa1c21867af2cf76e416c626..c288795f0d66b50cdaed64697252cdf309fad7de 100644 (file)
@@ -1,3 +1,106 @@
+require 'lockfile'
+require 'mime/types'
+require 'pathname'
+
+## time for some monkeypatching!
+class Lockfile
+  def gen_lock_id
+    Hash[
+         'host' => "#{ Socket.gethostname }",
+         'pid' => "#{ Process.pid }",
+         'ppid' => "#{ Process.ppid }",
+         'time' => timestamp,
+         'pname' => $0,
+         'user' => ENV["USER"]
+        ]
+  end
+
+  def dump_lock_id lock_id = @lock_id
+      "host: %s\npid: %s\nppid: %s\ntime: %s\nuser: %s\npname: %s\n" %
+        lock_id.values_at('host','pid','ppid','time','user', 'pname')
+    end
+
+  def lockinfo_on_disk
+    h = load_lock_id IO.read(path)
+    h['mtime'] = File.mtime path
+    h
+  end
+
+  def touch_yourself; touch path end
+end
+
+class Pathname
+  def human_size
+    s =
+      begin
+        size
+      rescue SystemCallError
+        return "?"
+      end
+
+    if s < 1024
+      s.to_s + "b"
+    elsif s < (1024 * 1024)
+      (s / 1024).to_s + "k"
+    elsif s < (1024 * 1024 * 1024)
+      (s / 1024 / 1024).to_s + "m"
+    else
+      (s / 1024 / 1024 / 1024).to_s + "g"
+    end
+  end
+
+  def human_time
+    begin
+      ctime.strftime("%Y-%m-%d %H:%M")
+    rescue SystemCallError
+      "?"
+    end
+  end
+end
+
+## more monkeypatching!
+module RMail
+  class EncodingUnsupportedError < StandardError; end
+
+  class Message
+    def add_file_attachment fn
+      bfn = File.basename fn
+      a = Message.new
+      t = MIME::Types.type_for(bfn).first || MIME::Types.type_for("exe").first
+
+      a.header.add "Content-Disposition", "attachment; filename=#{bfn}"
+      a.header.add "Content-Type", "#{t.content_type}; name=#{bfn}"
+      a.header.add "Content-Transfer-Encoding", t.encoding
+      a.body =
+        case t.encoding
+        when "base64"
+          [IO.read(fn)].pack "m"
+        when "quoted-printable"
+          [IO.read(fn)].pack "M"
+        when "7bit", "8bit"
+          IO.read(fn)
+        else
+          raise EncodingUnsupportedError, t.encoding
+        end
+
+      add_part a
+    end
+
+    def charset
+      if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/
+        $1
+      end
+    end
+  end
+end
+
+class Range
+  ## only valid for integer ranges (unless I guess it's exclusive)
+  def size 
+    last - first + (exclude_end? ? 0 : 1)
+  end
+end
+
 class Module
   def bool_reader *args
     args.each { |sym| class_eval %{ def #{sym}?; @#{sym}; end } }
@@ -9,7 +112,10 @@ class Module
   end
 
   def defer_all_other_method_calls_to obj
-    class_eval %{ def method_missing meth, *a, &b; @#{obj}.send meth, *a, &b; end }
+    class_eval %{
+      def method_missing meth, *a, &b; @#{obj}.send meth, *a, &b; end
+      def respond_to? meth; @#{obj}.respond_to?(meth); end
+    }
   end
 end
 
@@ -25,19 +131,7 @@ class Object
     ret
   end
 
-  ## takes a value which it yields and then returns, so that code
-  ## like:
-  ##
-  ## x = expensive_operation
-  ## log "got #{x}"
-  ## x
-  ##
-  ## now becomes:
-  ##
-  ## with(expensive_operation) { |x| log "got #{x}" }
-  ##
-  ## i'm sure there's pithy comment i could make here about the
-  ## superiority of lisp, but fuck lisp.
+  ## "k combinator"
   def returning x; yield x; x; end
 
   ## clone of java-style whole-method synchronization
@@ -174,6 +268,24 @@ module Enumerable
     end
     best
   end
+
+  ## returns the maximum shared prefix of an array of strings
+  ## optinally excluding a prefix
+  def shared_prefix caseless=false, exclude=""
+    return "" if empty?
+    prefix = ""
+    (0 ... first.length).each do |i|
+      c = (caseless ? first.downcase : first)[i]
+      break unless all? { |s| (caseless ? s.downcase : s)[i] == c }
+      next if exclude[i] == c
+      prefix += first[i].chr
+    end
+    prefix
+  end
+
+  def max_of
+    map { |e| yield e }.max
+  end
 end
 
 class Array
@@ -185,6 +297,8 @@ class Array
   def rest; self[1..-1]; end
 
   def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
+
+  def last= e; self[-1] = e end
 end
 
 class Time
@@ -271,6 +385,13 @@ module Singleton
     def deinstantiate!; @instance = nil; end
     def method_missing meth, *a, &b
       raise "no instance defined!" unless defined? @instance
+
+      ## if we've been deinstantiated, just drop all calls. this is
+      ## useful because threads that might be active during the
+      ## cleanup process (e.g. polling) would otherwise have to
+      ## special-case every call to a Singleton object
+      return nil if @instance.nil?
+
       @instance.send meth, *a, &b
     end
     def i_am_the_instance o
@@ -301,6 +422,9 @@ class Recoverable
   def id; __pass :id; end
   def to_s; __pass :to_s; end
   def to_yaml x; __pass :to_yaml, x; end
+  def is_a? c; @o.is_a? c; end
+
+  def respond_to? m; @o.respond_to? m end
 
   def __pass m, *a, &b
     begin
@@ -311,3 +435,39 @@ class Recoverable
     end
   end
 end
+
+## acts like a hash with an initialization block, but saves any
+## newly-created value even upon lookup.
+##
+## for example:
+##
+## class C
+##   attr_accessor :val
+##   def initialize; @val = 0 end
+## end
+## 
+## h = Hash.new { C.new }
+## h[:a].val # => 0
+## h[:a].val = 1
+## h[:a].val # => 0
+##
+## h2 = SavingHash.new { C.new }
+## h2[:a].val # => 0
+## h2[:a].val = 1
+## h2[:a].val # => 1
+##
+## important note: you REALLY want to use #member? to test existence,
+## because just checking h[anything] will always evaluate to true
+## (except for degenerate constructor blocks that return nil or false)
+class SavingHash
+  def initialize &b
+    @constructor = b
+    @hash = Hash.new
+  end
+
+  def [] k
+    @hash[k] ||= @constructor.call(k)
+  end
+
+  defer_all_other_method_calls_to :hash
+end