1999-08-13 01:45:20 -04:00
|
|
|
#
|
2002-12-19 15:00:19 -05:00
|
|
|
# tempfile - manipulates temporary files
|
1999-08-13 01:45:20 -04:00
|
|
|
#
|
2002-12-19 15:00:19 -05:00
|
|
|
# $Id$
|
2002-11-18 02:26:44 -05:00
|
|
|
#
|
1999-08-13 01:45:20 -04:00
|
|
|
|
|
|
|
require 'delegate'
|
2003-07-21 11:34:18 -04:00
|
|
|
require 'tmpdir'
|
2006-12-31 10:02:22 -05:00
|
|
|
require 'thread'
|
1999-08-13 01:45:20 -04:00
|
|
|
|
2009-08-26 08:50:57 -04:00
|
|
|
# A utility class for managing temporary files. When you create a Tempfile
|
|
|
|
# object, it will create a temporary file with a unique filename. A Tempfile
|
|
|
|
# objects behaves just like a File object, and you can perform all the usual
|
|
|
|
# file operations on it: reading data, writing data, changing its permissions,
|
|
|
|
# etc. So although this class does not explicitly document all instance methods
|
|
|
|
# supported by File, you can in fact call any File instance method on a
|
|
|
|
# Tempfile object.
|
|
|
|
#
|
|
|
|
# == Synopsis
|
|
|
|
#
|
|
|
|
# require 'tempfile'
|
2009-08-26 09:36:28 -04:00
|
|
|
#
|
2009-08-26 08:50:57 -04:00
|
|
|
# file = Tempfile.new('foo')
|
|
|
|
# file.path # => A unique filename in the OS's temp directory,
|
|
|
|
# # e.g.: "/tmp/foo.24722.0"
|
|
|
|
# # This filename contains 'foo' in its basename.
|
|
|
|
# file.write("hello world")
|
|
|
|
# file.rewind
|
|
|
|
# file.read # => "hello world"
|
|
|
|
# file.close
|
|
|
|
# file.unlink # deletes the temp file
|
|
|
|
#
|
|
|
|
# == Good practices
|
|
|
|
#
|
|
|
|
# === Explicit close
|
|
|
|
#
|
|
|
|
# When a Tempfile object is garbage collected, or when the Ruby interpreter
|
|
|
|
# exits, its associated temporary file is automatically deleted. This means
|
|
|
|
# that's it's unnecessary to explicitly delete a Tempfile after use, though
|
|
|
|
# it's good practice to do so: not explicitly deleting unused Tempfiles can
|
|
|
|
# potentially leave behind large amounts of tempfiles on the filesystem
|
|
|
|
# until they're garbage collected. The existance of these temp files can make
|
|
|
|
# it harder to determine a new Tempfile filename.
|
|
|
|
#
|
|
|
|
# Therefore, one should always call #unlink or close in an ensure block, like
|
|
|
|
# this:
|
|
|
|
#
|
2009-08-26 09:36:28 -04:00
|
|
|
# file = Tempfile.new('foo')
|
2009-08-26 08:50:57 -04:00
|
|
|
# begin
|
|
|
|
# ...do something with file...
|
|
|
|
# ensure
|
|
|
|
# file.close
|
|
|
|
# file.unlink # deletes the temp file
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# === Unlink after creation
|
|
|
|
#
|
|
|
|
# On POSIX systems, it's possible to unlink a file right after creating it,
|
|
|
|
# and before closing it. This removes the filesystem entry without closing
|
|
|
|
# the file handle, so it ensures that only the processes that already had
|
|
|
|
# the file handle open can access the file's contents. It's strongly
|
|
|
|
# recommended that you do this if you do not want any other processes to
|
|
|
|
# be able to read from or write to the Tempfile, and you do not need to
|
|
|
|
# know the Tempfile's filename either.
|
|
|
|
#
|
|
|
|
# For example, a practical use case for unlink-after-creation would be this:
|
|
|
|
# you need a large byte buffer that's too large to comfortably fit in RAM,
|
|
|
|
# e.g. when you're writing a web server and you want to buffer the client's
|
|
|
|
# file upload data.
|
|
|
|
#
|
|
|
|
# Please refer to #unlink for more information and a code example.
|
|
|
|
#
|
|
|
|
# == Minor notes
|
|
|
|
#
|
|
|
|
# Tempfile's filename picking method is both thread-safe and inter-process-safe:
|
|
|
|
# it guarantees that no other threads or processes will pick the same filename.
|
|
|
|
#
|
|
|
|
# Tempfile itself however may not be entirely thread-safe. If you access the
|
|
|
|
# same Tempfile object from multiple threads then you should protect it with a
|
|
|
|
# mutex.
|
2004-03-29 02:54:38 -05:00
|
|
|
class Tempfile < DelegateClass(File)
|
2009-08-26 01:34:47 -04:00
|
|
|
MAX_TRY = 10 # :nodoc:
|
2009-09-08 09:38:01 -04:00
|
|
|
include Dir::Tmpname
|
1999-08-13 01:45:20 -04:00
|
|
|
|
2009-08-26 08:50:57 -04:00
|
|
|
# call-seq:
|
|
|
|
# new(basename, [tmpdir = Dir.tmpdir], [options])
|
2007-09-11 04:28:29 -04:00
|
|
|
#
|
2009-08-26 08:50:57 -04:00
|
|
|
# Creates a temporary file with permissions 0600 (= only readable and
|
|
|
|
# writable by the owner) and opens it with mode "w+".
|
2002-12-19 15:00:19 -05:00
|
|
|
#
|
2009-08-26 08:50:57 -04:00
|
|
|
# The +basename+ parameter is used to determine the name of the
|
|
|
|
# temporary file. You can either pass a String or an Array with
|
|
|
|
# 2 String elements. In the former form, the temporary file's base
|
|
|
|
# name will begin with the given string. In the latter form,
|
|
|
|
# the temporary file's base name will begin with the array's first
|
|
|
|
# element, and end with the second element. For example:
|
|
|
|
#
|
|
|
|
# file = Tempfile.new('hello')
|
|
|
|
# file.path # => something like: "/tmp/foo2843-8392-92849382--0"
|
2009-08-26 09:36:28 -04:00
|
|
|
#
|
2009-08-26 08:50:57 -04:00
|
|
|
# # Use the Array form to enforce an extension in the filename:
|
|
|
|
# file = Tempfile.new(['hello', '.jpg'])
|
|
|
|
# file.path # => something like: "/tmp/foo2843-8392-92849382--0.jpg"
|
|
|
|
#
|
|
|
|
# The temporary file will be placed in the directory as specified
|
|
|
|
# by the +tmpdir+ parameter. By default, this is +Dir.tmpdir+.
|
|
|
|
# When $SAFE > 0 and the given +tmpdir+ is tainted, it uses
|
|
|
|
# '/tmp' as the temporary directory. Please note that ENV values
|
|
|
|
# are tainted by default, and +Dir.tmpdir+'s return value might
|
|
|
|
# come from environment variables (e.g. <tt>$TMPDIR</tt>).
|
|
|
|
#
|
|
|
|
# file = Tempfile.new('hello', '/home/aisaka')
|
|
|
|
# file.path # => something like: "/home/aisaka/foo2843-8392-92849382--0"
|
|
|
|
#
|
|
|
|
# You can also pass an options hash. Under the hood, Tempfile creates
|
|
|
|
# the temporary file using +File.open+. These options will be passed to
|
|
|
|
# +File.open+. This is mostly useful for specifying encoding
|
|
|
|
# options, e.g.:
|
|
|
|
#
|
|
|
|
# Tempfile.new('hello', '/home/aisaka', :encoding => 'ascii-8bit')
|
2009-08-26 09:36:28 -04:00
|
|
|
#
|
2009-08-26 08:50:57 -04:00
|
|
|
# # You can also omit the 'tmpdir' parameter:
|
|
|
|
# Tempfile.new('hello', :encoding => 'ascii-8bit')
|
|
|
|
#
|
|
|
|
# === Exceptions
|
2009-08-26 09:36:28 -04:00
|
|
|
#
|
2009-08-28 14:11:37 -04:00
|
|
|
# If Tempfile.new cannot find a unique filename within a limited
|
|
|
|
# number of tries, then it will raise an exception.
|
2008-10-18 06:32:26 -04:00
|
|
|
def initialize(basename, *rest)
|
2009-09-08 09:38:01 -04:00
|
|
|
@data = []
|
2010-02-12 22:20:52 -05:00
|
|
|
@clean_proc = Remover.new(@data)
|
2009-09-08 09:38:01 -04:00
|
|
|
ObjectSpace.define_finalizer(self, @clean_proc)
|
2002-11-17 13:01:17 -05:00
|
|
|
|
2009-09-08 09:38:01 -04:00
|
|
|
create(basename, *rest) do |tmpname, n, opts|
|
|
|
|
lock = tmpname + '.lock'
|
2009-11-11 16:42:40 -05:00
|
|
|
mode = File::RDWR|File::CREAT|File::EXCL
|
|
|
|
perm = 0600
|
|
|
|
if opts
|
|
|
|
mode |= opts.delete(:mode) || 0
|
|
|
|
opts[:perm] = perm
|
|
|
|
else
|
|
|
|
opts = perm
|
|
|
|
end
|
2009-09-08 09:38:01 -04:00
|
|
|
self.class.mkdir(lock)
|
2001-05-06 11:06:00 -04:00
|
|
|
begin
|
2009-11-11 16:42:40 -05:00
|
|
|
@data[1] = @tmpfile = File.open(tmpname, mode, opts)
|
2009-09-08 09:38:01 -04:00
|
|
|
@data[0] = @tmpname = tmpname
|
|
|
|
ensure
|
|
|
|
self.class.rmdir(lock)
|
2006-12-31 10:02:22 -05:00
|
|
|
end
|
2008-10-18 06:32:26 -04:00
|
|
|
end
|
2002-11-17 13:01:17 -05:00
|
|
|
|
2001-05-06 11:06:00 -04:00
|
|
|
super(@tmpfile)
|
1999-08-13 01:45:20 -04:00
|
|
|
end
|
|
|
|
|
2002-12-19 15:00:19 -05:00
|
|
|
# Opens or reopens the file with mode "r+".
|
1999-08-13 01:45:20 -04:00
|
|
|
def open
|
|
|
|
@tmpfile.close if @tmpfile
|
|
|
|
@tmpfile = File.open(@tmpname, 'r+')
|
2002-11-17 13:01:17 -05:00
|
|
|
@data[1] = @tmpfile
|
1999-08-13 01:45:20 -04:00
|
|
|
__setobj__(@tmpfile)
|
|
|
|
end
|
|
|
|
|
2002-12-19 15:00:19 -05:00
|
|
|
def _close # :nodoc:
|
1999-08-13 01:45:20 -04:00
|
|
|
@tmpfile.close if @tmpfile
|
2004-03-23 20:08:20 -05:00
|
|
|
@tmpfile = nil
|
|
|
|
@data[1] = nil if @data
|
2004-03-23 14:14:16 -05:00
|
|
|
end
|
2002-12-19 14:01:59 -05:00
|
|
|
protected :_close
|
|
|
|
|
2009-08-26 08:50:57 -04:00
|
|
|
# Closes the file. If +unlink_now+ is true, then the file will be unlinked
|
|
|
|
# (deleted) after closing. Of course, you can choose to later call #unlink
|
|
|
|
# if you do not unlink it now.
|
2002-12-19 15:00:19 -05:00
|
|
|
#
|
|
|
|
# If you don't explicitly unlink the temporary file, the removal
|
|
|
|
# will be delayed until the object is finalized.
|
2002-12-23 09:48:14 -05:00
|
|
|
def close(unlink_now=false)
|
|
|
|
if unlink_now
|
2002-12-19 14:01:59 -05:00
|
|
|
close!
|
|
|
|
else
|
|
|
|
_close
|
1999-08-13 01:45:20 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-08-26 08:50:57 -04:00
|
|
|
# Closes and unlinks (deletes) the file. Has the same effect as called
|
|
|
|
# <tt>close(true)</tt>.
|
2002-12-19 14:01:59 -05:00
|
|
|
def close!
|
|
|
|
_close
|
2009-08-26 01:34:47 -04:00
|
|
|
unlink
|
2009-08-26 05:49:28 -04:00
|
|
|
ObjectSpace.undefine_finalizer(self)
|
2002-12-19 14:01:59 -05:00
|
|
|
end
|
|
|
|
|
2009-08-26 08:50:57 -04:00
|
|
|
# Unlinks (deletes) the file from the filesystem. One should always unlink
|
|
|
|
# the file after using it, as is explained in the "Explicit close" good
|
|
|
|
# practice section in the Tempfile overview:
|
|
|
|
#
|
2009-08-26 09:36:28 -04:00
|
|
|
# file = Tempfile.new('foo')
|
2009-08-26 08:50:57 -04:00
|
|
|
# begin
|
|
|
|
# ...do something with file...
|
|
|
|
# ensure
|
|
|
|
# file.close
|
|
|
|
# file.unlink # deletes the temp file
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# === Unlink-before-close
|
|
|
|
#
|
|
|
|
# On POSIX systems it's possible to unlink a file before closing it. This
|
|
|
|
# practice is explained in detail in the Tempfile overview (section
|
|
|
|
# "Unlink after creation"); please refer there for more information.
|
|
|
|
#
|
|
|
|
# However, unlink-before-close may not be supported on non-POSIX operating
|
|
|
|
# systems. Microsoft Windows is the most notable case: unlinking a non-closed
|
|
|
|
# file will result in an error, which this method will silently ignore. If
|
|
|
|
# you want to practice unlink-before-close whenever possible, then you should
|
|
|
|
# write code like this:
|
|
|
|
#
|
|
|
|
# file = Tempfile.new('foo')
|
|
|
|
# file.unlink # On Windows this silently fails.
|
|
|
|
# begin
|
|
|
|
# ... do something with file ...
|
|
|
|
# ensure
|
|
|
|
# file.close! # Closes the file handle. If the file wasn't unlinked
|
|
|
|
# # because #unlink failed, then this method will attempt
|
|
|
|
# # to do so again.
|
|
|
|
# end
|
2002-12-19 14:01:59 -05:00
|
|
|
def unlink
|
|
|
|
# keep this order for thread safeness
|
2009-08-26 01:34:47 -04:00
|
|
|
return unless @tmpname
|
2004-05-07 04:44:24 -04:00
|
|
|
begin
|
2009-05-19 20:35:30 -04:00
|
|
|
if File.exist?(@tmpname)
|
|
|
|
File.unlink(@tmpname)
|
|
|
|
end
|
2010-02-12 22:20:52 -05:00
|
|
|
# remove tmpname from remover
|
2009-08-26 01:34:47 -04:00
|
|
|
@data[0] = @data[2] = nil
|
2004-10-19 06:25:23 -04:00
|
|
|
@data = @tmpname = nil
|
2005-05-10 21:46:31 -04:00
|
|
|
rescue Errno::EACCES
|
2004-05-07 04:44:24 -04:00
|
|
|
# may not be able to unlink on Windows; just ignore
|
|
|
|
end
|
2002-12-19 14:01:59 -05:00
|
|
|
end
|
|
|
|
alias delete unlink
|
|
|
|
|
2002-12-19 15:00:19 -05:00
|
|
|
# Returns the full path name of the temporary file.
|
2009-08-26 01:34:47 -04:00
|
|
|
# This will be nil if #unlink has been called.
|
1999-08-13 01:45:20 -04:00
|
|
|
def path
|
|
|
|
@tmpname
|
|
|
|
end
|
2002-06-04 03:34:19 -04:00
|
|
|
|
2002-12-19 15:00:19 -05:00
|
|
|
# Returns the size of the temporary file. As a side effect, the IO
|
|
|
|
# buffer is flushed before determining the size.
|
2002-06-04 03:34:19 -04:00
|
|
|
def size
|
|
|
|
if @tmpfile
|
|
|
|
@tmpfile.flush
|
|
|
|
@tmpfile.stat.size
|
|
|
|
else
|
|
|
|
0
|
|
|
|
end
|
|
|
|
end
|
2002-12-19 15:00:19 -05:00
|
|
|
alias length size
|
|
|
|
|
2010-02-12 22:20:52 -05:00
|
|
|
# :stopdoc:
|
|
|
|
class Remover
|
|
|
|
def initialize(data)
|
|
|
|
@pid = $$
|
|
|
|
@data = data
|
|
|
|
end
|
|
|
|
|
|
|
|
def call(*args)
|
|
|
|
if @pid == $$
|
|
|
|
path, tmpfile = *@data
|
2002-12-19 15:00:19 -05:00
|
|
|
|
2010-02-12 22:20:52 -05:00
|
|
|
STDERR.print "removing ", path, "..." if $DEBUG
|
2002-12-19 15:00:19 -05:00
|
|
|
|
2010-02-12 22:20:52 -05:00
|
|
|
tmpfile.close if tmpfile
|
2002-12-19 15:00:19 -05:00
|
|
|
|
2010-02-12 22:20:52 -05:00
|
|
|
# keep this order for thread safeness
|
|
|
|
if path
|
|
|
|
File.unlink(path) if File.exist?(path)
|
|
|
|
end
|
2002-12-19 15:00:19 -05:00
|
|
|
|
2010-02-12 22:20:52 -05:00
|
|
|
STDERR.print "done\n" if $DEBUG
|
|
|
|
end
|
2002-12-19 15:00:19 -05:00
|
|
|
end
|
2010-02-12 22:20:52 -05:00
|
|
|
end
|
|
|
|
# :startdoc:
|
2002-12-19 15:00:19 -05:00
|
|
|
|
2010-02-12 22:20:52 -05:00
|
|
|
class << self
|
2009-08-26 08:50:57 -04:00
|
|
|
# Creates a new Tempfile.
|
|
|
|
#
|
|
|
|
# If no block is given, this is a synonym for Tempfile.new.
|
|
|
|
#
|
|
|
|
# If a block is given, then a Tempfile object will be constructed,
|
|
|
|
# and the block is run with said object as argument. The Tempfile
|
|
|
|
# oject will be automatically closed after the block terminates.
|
|
|
|
# The call returns the value of the block.
|
|
|
|
#
|
|
|
|
# In any case, all arguments (+*args+) will be passed to Tempfile.new.
|
2003-01-20 07:27:53 -05:00
|
|
|
#
|
2009-08-26 08:50:57 -04:00
|
|
|
# Tempfile.open('foo', '/home/temp') do |f|
|
|
|
|
# ... do something with f ...
|
|
|
|
# end
|
2009-08-26 09:36:28 -04:00
|
|
|
#
|
2009-08-26 08:50:57 -04:00
|
|
|
# # Equivalent:
|
|
|
|
# f = Tempfile.open('foo', '/home/temp')
|
|
|
|
# begin
|
|
|
|
# ... do something with f ...
|
|
|
|
# ensure
|
|
|
|
# f.close
|
|
|
|
# end
|
2002-12-19 15:00:19 -05:00
|
|
|
def open(*args)
|
2003-01-20 07:27:53 -05:00
|
|
|
tempfile = new(*args)
|
|
|
|
|
|
|
|
if block_given?
|
|
|
|
begin
|
|
|
|
yield(tempfile)
|
|
|
|
ensure
|
|
|
|
tempfile.close
|
|
|
|
end
|
|
|
|
else
|
|
|
|
tempfile
|
|
|
|
end
|
2002-12-19 15:00:19 -05:00
|
|
|
end
|
2009-09-08 09:38:01 -04:00
|
|
|
|
|
|
|
def mkdir(*args)
|
|
|
|
Dir.mkdir(*args)
|
|
|
|
end
|
|
|
|
def rmdir(*args)
|
|
|
|
Dir.rmdir(*args)
|
|
|
|
end
|
2002-12-19 15:00:19 -05:00
|
|
|
end
|
1999-08-13 01:45:20 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
if __FILE__ == $0
|
|
|
|
# $DEBUG = true
|
|
|
|
f = Tempfile.new("foo")
|
|
|
|
f.print("foo\n")
|
|
|
|
f.close
|
|
|
|
f.open
|
|
|
|
p f.gets # => "foo\n"
|
2002-12-19 14:01:59 -05:00
|
|
|
f.close!
|
1999-08-13 01:45:20 -04:00
|
|
|
end
|