require 'delegate'
require 'tmpdir'
class Tempfile < DelegateClass(File)
MAX_TRY = 10
@@cleanlist = []
def initialize(basename, tmpdir=Dir::tmpdir)
if $SAFE > 0 and tmpdir.tainted?
tmpdir = '/tmp'
end
lock = nil
n = failure = 0
begin
Thread.critical = true
begin
tmpname = File.join(tmpdir, make_tmpname(basename, n))
lock = tmpname + '.lock'
n += 1
end while @@cleanlist.include?(tmpname) or
File.exist?(lock) or File.exist?(tmpname)
Dir.mkdir(lock)
rescue
failure += 1
retry if failure < MAX_TRY
raise "cannot generate tempfile `%s'" % tmpname
ensure
Thread.critical = false
end
@data = [tmpname]
@clean_proc = Tempfile.callback(@data)
ObjectSpace.define_finalizer(self, @clean_proc)
@tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
@tmpname = tmpname
@@cleanlist << @tmpname
@data[1] = @tmpfile
@data[2] = @@cleanlist
super(@tmpfile)
Dir.rmdir(lock)
end
def make_tmpname(basename, n)
case basename
when Array
prefix, suffix = *basename
else
prefix, suffix = basename, ''
end
t = Time.now.strftime("%Y%m%d")
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
end
private :make_tmpname
def open
@tmpfile.close if @tmpfile
@tmpfile = File.open(@tmpname, 'r+')
@data[1] = @tmpfile
__setobj__(@tmpfile)
end
def _close # :nodoc:
@tmpfile.close if @tmpfile
@tmpfile = nil
@data[1] = nil if @data
end
protected :_close
def close(unlink_now=false)
if unlink_now
close!
else
_close
end
end
def close!
_close
@clean_proc.call
ObjectSpace.undefine_finalizer(self)
@data = @tmpname = nil
end
def unlink
begin
File.unlink(@tmpname) if File.exist?(@tmpname)
@@cleanlist.delete(@tmpname)
@data = @tmpname = nil
ObjectSpace.undefine_finalizer(self)
rescue Errno::EACCES
end
end
alias delete unlink
def path
@tmpname
end
def size
if @tmpfile
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
alias length size
class << self
def callback(data) pid = $$
lambda{
if pid == $$
path, tmpfile, cleanlist = *data
print "removing ", path, "..." if $DEBUG
tmpfile.close if tmpfile
File.unlink(path) if File.exist?(path)
cleanlist.delete(path) if cleanlist
print "done\n" if $DEBUG
end
}
end
def open(*args)
tempfile = new(*args)
if block_given?
begin
yield(tempfile)
ensure
tempfile.close
end
nil
else
tempfile
end
end
end
end
if __FILE__ == $0
f = Tempfile.new("foo")
f.print("foo\n")
f.close
f.open
p f.gets f.close!
end