Skip to content
This repository
Newer
Older
100644 346 lines (324 sloc) 10.517 kb
511dc44a » Laurent Sansonetti
2008-02-25 initial import
1 #
2 # tempfile - manipulates temporary files
3 #
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
4 # $Id: tempfile.rb 27578 2010-05-01 13:54:01Z nobu $
511dc44a » Laurent Sansonetti
2008-02-25 initial import
5 #
6
7 require 'delegate'
8 require 'tmpdir'
9 require 'thread'
10
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
11 # A utility class for managing temporary files. When you create a Tempfile
12 # object, it will create a temporary file with a unique filename. A Tempfile
13 # objects behaves just like a File object, and you can perform all the usual
14 # file operations on it: reading data, writing data, changing its permissions,
15 # etc. So although this class does not explicitly document all instance methods
16 # supported by File, you can in fact call any File instance method on a
17 # Tempfile object.
18 #
19 # == Synopsis
20 #
21 # require 'tempfile'
22 #
23 # file = Tempfile.new('foo')
24 # file.path # => A unique filename in the OS's temp directory,
25 # # e.g.: "/tmp/foo.24722.0"
26 # # This filename contains 'foo' in its basename.
27 # file.write("hello world")
28 # file.rewind
29 # file.read # => "hello world"
30 # file.close
31 # file.unlink # deletes the temp file
32 #
33 # == Good practices
34 #
35 # === Explicit close
36 #
37 # When a Tempfile object is garbage collected, or when the Ruby interpreter
38 # exits, its associated temporary file is automatically deleted. This means
39 # that's it's unnecessary to explicitly delete a Tempfile after use, though
40 # it's good practice to do so: not explicitly deleting unused Tempfiles can
41 # potentially leave behind large amounts of tempfiles on the filesystem
42 # until they're garbage collected. The existance of these temp files can make
43 # it harder to determine a new Tempfile filename.
44 #
45 # Therefore, one should always call #unlink or close in an ensure block, like
46 # this:
47 #
48 # file = Tempfile.new('foo')
49 # begin
50 # ...do something with file...
51 # ensure
52 # file.close
53 # file.unlink # deletes the temp file
54 # end
55 #
56 # === Unlink after creation
57 #
58 # On POSIX systems, it's possible to unlink a file right after creating it,
59 # and before closing it. This removes the filesystem entry without closing
60 # the file handle, so it ensures that only the processes that already had
61 # the file handle open can access the file's contents. It's strongly
62 # recommended that you do this if you do not want any other processes to
63 # be able to read from or write to the Tempfile, and you do not need to
64 # know the Tempfile's filename either.
65 #
66 # For example, a practical use case for unlink-after-creation would be this:
67 # you need a large byte buffer that's too large to comfortably fit in RAM,
68 # e.g. when you're writing a web server and you want to buffer the client's
69 # file upload data.
70 #
71 # Please refer to #unlink for more information and a code example.
72 #
73 # == Minor notes
74 #
75 # Tempfile's filename picking method is both thread-safe and inter-process-safe:
76 # it guarantees that no other threads or processes will pick the same filename.
77 #
78 # Tempfile itself however may not be entirely thread-safe. If you access the
79 # same Tempfile object from multiple threads then you should protect it with a
80 # mutex.
511dc44a » Laurent Sansonetti
2008-02-25 initial import
81 class Tempfile < DelegateClass(File)
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
82 MAX_TRY = 10 # :nodoc:
83 include Dir::Tmpname
84
85 # call-seq:
86 # new(basename, [tmpdir = Dir.tmpdir], [options])
87 #
88 # Creates a temporary file with permissions 0600 (= only readable and
89 # writable by the owner) and opens it with mode "w+".
90 #
91 # The +basename+ parameter is used to determine the name of the
92 # temporary file. You can either pass a String or an Array with
93 # 2 String elements. In the former form, the temporary file's base
94 # name will begin with the given string. In the latter form,
95 # the temporary file's base name will begin with the array's first
96 # element, and end with the second element. For example:
97 #
98 # file = Tempfile.new('hello')
99 # file.path # => something like: "/tmp/foo2843-8392-92849382--0"
100 #
101 # # Use the Array form to enforce an extension in the filename:
102 # file = Tempfile.new(['hello', '.jpg'])
103 # file.path # => something like: "/tmp/foo2843-8392-92849382--0.jpg"
104 #
105 # The temporary file will be placed in the directory as specified
106 # by the +tmpdir+ parameter. By default, this is +Dir.tmpdir+.
107 # When $SAFE > 0 and the given +tmpdir+ is tainted, it uses
108 # '/tmp' as the temporary directory. Please note that ENV values
109 # are tainted by default, and +Dir.tmpdir+'s return value might
110 # come from environment variables (e.g. <tt>$TMPDIR</tt>).
111 #
112 # file = Tempfile.new('hello', '/home/aisaka')
113 # file.path # => something like: "/home/aisaka/foo2843-8392-92849382--0"
114 #
115 # You can also pass an options hash. Under the hood, Tempfile creates
116 # the temporary file using +File.open+. These options will be passed to
117 # +File.open+. This is mostly useful for specifying encoding
118 # options, e.g.:
119 #
120 # Tempfile.new('hello', '/home/aisaka', :encoding => 'ascii-8bit')
121 #
122 # # You can also omit the 'tmpdir' parameter:
123 # Tempfile.new('hello', :encoding => 'ascii-8bit')
124 #
125 # === Exceptions
126 #
127 # If Tempfile.new cannot find a unique filename within a limited
128 # number of tries, then it will raise an exception.
8f211620 » richkilmer
2009-03-02 bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues usi…
129 def initialize(basename, *rest)
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
130 @data = []
131 @clean_proc = Remover.new(@data)
132 ObjectSpace.define_finalizer(self, @clean_proc)
511dc44a » Laurent Sansonetti
2008-02-25 initial import
133
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
134 create(basename, *rest) do |tmpname, n, opts|
135 lock = tmpname + '.lock'
136 mode = File::RDWR|File::CREAT|File::EXCL
137 perm = 0600
138 if opts
139 mode |= opts.delete(:mode) || 0
140 opts[:perm] = perm
141 perm = nil
142 else
143 opts = perm
144 end
145 self.class.mkdir(lock)
511dc44a » Laurent Sansonetti
2008-02-25 initial import
146 begin
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
147 @data[1] = @tmpfile = File.open(tmpname, mode, opts)
148 @data[0] = @tmpname = tmpname
149 ensure
150 self.class.rmdir(lock)
511dc44a » Laurent Sansonetti
2008-02-25 initial import
151 end
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
152 @mode = mode & ~(File::CREAT|File::EXCL)
153 perm or opts.freeze
154 @opts = opts
8f211620 » richkilmer
2009-03-02 bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues usi…
155 end
511dc44a » Laurent Sansonetti
2008-02-25 initial import
156
157 super(@tmpfile)
158 end
159
160 # Opens or reopens the file with mode "r+".
161 def open
162 @tmpfile.close if @tmpfile
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
163 @tmpfile = File.open(@tmpname, @mode, @opts)
511dc44a » Laurent Sansonetti
2008-02-25 initial import
164 @data[1] = @tmpfile
165 __setobj__(@tmpfile)
166 end
167
168 def _close # :nodoc:
169 @tmpfile.close if @tmpfile
170 @tmpfile = nil
171 @data[1] = nil if @data
172 end
173 protected :_close
174
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
175 # Closes the file. If +unlink_now+ is true, then the file will be unlinked
176 # (deleted) after closing. Of course, you can choose to later call #unlink
177 # if you do not unlink it now.
511dc44a » Laurent Sansonetti
2008-02-25 initial import
178 #
179 # If you don't explicitly unlink the temporary file, the removal
180 # will be delayed until the object is finalized.
181 def close(unlink_now=false)
182 if unlink_now
183 close!
184 else
185 _close
186 end
187 end
188
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
189 # Closes and unlinks (deletes) the file. Has the same effect as called
190 # <tt>close(true)</tt>.
511dc44a » Laurent Sansonetti
2008-02-25 initial import
191 def close!
192 _close
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
193 unlink
511dc44a » Laurent Sansonetti
2008-02-25 initial import
194 ObjectSpace.undefine_finalizer(self)
195 end
196
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
197 # Unlinks (deletes) the file from the filesystem. One should always unlink
198 # the file after using it, as is explained in the "Explicit close" good
199 # practice section in the Tempfile overview:
200 #
201 # file = Tempfile.new('foo')
202 # begin
203 # ...do something with file...
204 # ensure
205 # file.close
206 # file.unlink # deletes the temp file
207 # end
208 #
209 # === Unlink-before-close
210 #
211 # On POSIX systems it's possible to unlink a file before closing it. This
212 # practice is explained in detail in the Tempfile overview (section
213 # "Unlink after creation"); please refer there for more information.
214 #
215 # However, unlink-before-close may not be supported on non-POSIX operating
216 # systems. Microsoft Windows is the most notable case: unlinking a non-closed
217 # file will result in an error, which this method will silently ignore. If
218 # you want to practice unlink-before-close whenever possible, then you should
219 # write code like this:
220 #
221 # file = Tempfile.new('foo')
222 # file.unlink # On Windows this silently fails.
223 # begin
224 # ... do something with file ...
225 # ensure
226 # file.close! # Closes the file handle. If the file wasn't unlinked
227 # # because #unlink failed, then this method will attempt
228 # # to do so again.
229 # end
511dc44a » Laurent Sansonetti
2008-02-25 initial import
230 def unlink
231 # keep this order for thread safeness
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
232 return unless @tmpname
511dc44a » Laurent Sansonetti
2008-02-25 initial import
233 begin
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
234 if File.exist?(@tmpname)
235 File.unlink(@tmpname)
236 end
237 # remove tmpname from remover
238 @data[0] = @data[2] = nil
511dc44a » Laurent Sansonetti
2008-02-25 initial import
239 @data = @tmpname = nil
240 rescue Errno::EACCES
241 # may not be able to unlink on Windows; just ignore
242 end
243 end
244 alias delete unlink
245
246 # Returns the full path name of the temporary file.
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
247 # This will be nil if #unlink has been called.
511dc44a » Laurent Sansonetti
2008-02-25 initial import
248 def path
249 @tmpname
250 end
251
252 # Returns the size of the temporary file. As a side effect, the IO
253 # buffer is flushed before determining the size.
254 def size
255 if @tmpfile
256 @tmpfile.flush
257 @tmpfile.stat.size
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
258 elsif @tmpname
259 File.size(@tmpname)
511dc44a » Laurent Sansonetti
2008-02-25 initial import
260 else
261 0
262 end
263 end
264 alias length size
265
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
266 # :stopdoc:
267 class Remover
268 def initialize(data)
269 @pid = $$
270 @data = data
271 end
511dc44a » Laurent Sansonetti
2008-02-25 initial import
272
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
273 def call(*args)
274 if @pid == $$
275 path, tmpfile = *@data
511dc44a » Laurent Sansonetti
2008-02-25 initial import
276
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
277 STDERR.print "removing ", path, "..." if $DEBUG
511dc44a » Laurent Sansonetti
2008-02-25 initial import
278
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
279 tmpfile.close if tmpfile
511dc44a » Laurent Sansonetti
2008-02-25 initial import
280
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
281 # keep this order for thread safeness
282 if path
283 File.unlink(path) if File.exist?(path)
284 end
285
286 STDERR.print "done\n" if $DEBUG
287 end
511dc44a » Laurent Sansonetti
2008-02-25 initial import
288 end
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
289 end
290 # :startdoc:
511dc44a » Laurent Sansonetti
2008-02-25 initial import
291
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
292 class << self
293 # Creates a new Tempfile.
511dc44a » Laurent Sansonetti
2008-02-25 initial import
294 #
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
295 # If no block is given, this is a synonym for Tempfile.new.
296 #
297 # If a block is given, then a Tempfile object will be constructed,
298 # and the block is run with said object as argument. The Tempfile
299 # oject will be automatically closed after the block terminates.
300 # The call returns the value of the block.
301 #
302 # In any case, all arguments (+*args+) will be passed to Tempfile.new.
303 #
304 # Tempfile.open('foo', '/home/temp') do |f|
305 # ... do something with f ...
306 # end
307 #
308 # # Equivalent:
309 # f = Tempfile.open('foo', '/home/temp')
310 # begin
311 # ... do something with f ...
312 # ensure
313 # f.close
314 # end
511dc44a » Laurent Sansonetti
2008-02-25 initial import
315 def open(*args)
316 tempfile = new(*args)
317
318 if block_given?
319 begin
320 yield(tempfile)
321 ensure
322 tempfile.close
323 end
324 else
325 tempfile
326 end
327 end
467bc1b0 » Thibault Martin-Lagardette
2010-05-18 Update library and removing working tags
328
329 def mkdir(*args)
330 Dir.mkdir(*args)
331 end
332 def rmdir(*args)
333 Dir.rmdir(*args)
334 end
511dc44a » Laurent Sansonetti
2008-02-25 initial import
335 end
336 end
337
338 if __FILE__ == $0
339 # $DEBUG = true
340 f = Tempfile.new("foo")
341 f.print("foo\n")
342 f.close
343 f.open
344 p f.gets # => "foo\n"
345 f.close!
346 end
Something went wrong with that request. Please try again.