package org.jruby.ext.tempfile;
import jnr.constants.platform.Errno;
import jnr.constants.platform.OpenFlags;
import jnr.posix.POSIX;
import org.jruby.Finalizable;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyException;
import org.jruby.RubyFile;
import org.jruby.RubyFileStat;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyString;
import org.jruby.RubySystemCallError;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.platform.Platform;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockCallback;
import org.jruby.runtime.CallBlock19;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.Signature;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import java.io.File;
import java.io.IOException;
@JRubyClass(name="Tempfile", parent="File")
public class Tempfile extends RubyFile implements Finalizable {
private static ObjectAllocator TEMPFILE_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new Tempfile(runtime, klass);
}
};
public static RubyClass createTempfileClass(Ruby runtime) {
RubyClass tempfileClass = runtime.defineClass("Tempfile", runtime.getFile(), TEMPFILE_ALLOCATOR);
tempfileClass.defineAnnotatedMethods(Tempfile.class);
return tempfileClass;
}
private File tmpFile = null;
private IRubyObject tmpname;
private IRubyObject opts;
private IRubyObject mode;
public Tempfile(Ruby runtime, RubyClass type) {
super(runtime, type);
}
@JRubyMethod(optional = 4, visibility = Visibility.PRIVATE)
@Override
public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
if (args.length == 0) {
args = new IRubyObject[] {RubyString.newEmptyString(context.runtime)};
}
return initializeCommon(context, args);
}
private IRubyObject initializeCommon(ThreadContext context, IRubyObject[] args) {
BlockCallback body = new TempfileCallback();
callMethod(context, "create", args, CallBlock19.newCallClosure(this, getMetaClass(), Signature.OPTIONAL, body, context));
context.runtime.addInternalFinalizer(Tempfile.this);
return context.nil;
}
private class TempfileCallback implements BlockCallback {
@Override
public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
Ruby runtime = context.runtime;
IRubyObject tmpname = args[0], opts = args.length > 2 ? args[2] : context.nil;
int mode = OpenFlags.O_RDWR.intValue() | OpenFlags.O_EXCL.intValue();
IRubyObject perm = runtime.newFixnum(0600);
if (!opts.isNil()) {
RubyHash options = (RubyHash)opts;
IRubyObject optsMode = options.delete(context, runtime.newSymbol("mode"), Block.NULL_BLOCK);
if (!optsMode.isNil()) {
mode |= optsMode.convertToInteger().getIntValue();
}
options.op_aset(context, runtime.newSymbol("perm"), perm);
} else {
opts = perm;
}
try {
File tmp = new File(tmpname.convertToString().toString());
if (tmp.createNewFile()) {
runtime.getPosix().chmod(tmp.getAbsolutePath(), 0600);
tmpFile = tmp;
} else {
throw context.runtime.newErrnoEEXISTError(getPath());
}
} catch (IOException e) {
throw context.runtime.newIOErrorFromException(e);
}
Tempfile.super.initialize(context, new IRubyObject[]{tmpname, runtime.newFixnum(mode), opts}, Block.NULL_BLOCK);
Tempfile.this.tmpname = tmpname;
Tempfile.this.mode = runtime.newFixnum(mode & ~(OpenFlags.O_CREAT.intValue() | OpenFlags.O_EXCL.intValue()));
Tempfile.this.opts = opts;
return opts;
}
}
@JRubyMethod
public IRubyObject open(ThreadContext context) {
if (!isClosed()) rbIoClose(context);
openFile = null;
Tempfile.super.initialize(context, new IRubyObject[]{tmpname, mode, opts}, Block.NULL_BLOCK);
return this;
}
@JRubyMethod(visibility = Visibility.PROTECTED)
public IRubyObject _close(ThreadContext context) {
return !isClosed() ? super.close(context) : context.nil;
}
@JRubyMethod(optional = 1)
public IRubyObject close(ThreadContext context, IRubyObject[] args, Block block) {
boolean unlink = args.length == 1 ? args[0].isTrue() : false;
return unlink ? close_bang(context) : _close(context);
}
@JRubyMethod(name = "close!")
public IRubyObject close_bang(ThreadContext context) {
_close(context);
unlink(context);
return context.nil;
}
@JRubyMethod(name = {"unlink", "delete"})
public IRubyObject unlink(ThreadContext context) {
if (openFile.getPath() == null) return context.nil;
Ruby runtime = context.runtime;
POSIX posix = runtime.getPosix();
if (posix.isNative() && !Platform.IS_WINDOWS) {
IRubyObject oldExc = context.runtime.getGlobalVariables().get("$!");
try {
RubyFile.unlink(context, this);
} catch (RaiseException re) {
RubyException excp = re.getException();
if (!(excp instanceof RubySystemCallError)) throw re;
int errno = (int)((RubySystemCallError)excp).errno().convertToInteger().getLongValue();
if (errno != Errno.ENOENT.intValue() && errno != Errno.EACCES.intValue()) {
throw re;
}
context.runtime.getGlobalVariables().set("$!", oldExc);
}
openFile.setPath(null);
tmpname = context.nil;
} else {
if (isClosed()) {
if (!tmpFile.exists() || tmpFile.delete()) {
openFile.setPath(null);
tmpname = context.nil;
}
} else {
context.runtime.getWarnings().warn("Tempfile#unlink or delete called on open file; ignoring");
}
}
return context.nil;
}
@JRubyMethod(name = {"size", "length"})
@Override
public IRubyObject size(ThreadContext context) {
if (!isClosed()) {
flush(context);
RubyFileStat stat = (RubyFileStat)stat(context);
return stat.size();
} else if (tmpname != null && !tmpname.isNil()) {
RubyFileStat stat = (RubyFileStat)stat(context, getMetaClass(), tmpname);
return stat.size();
} else {
return RubyFixnum.zero(context.runtime);
}
}
@Deprecated
public static IRubyObject open19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
return open(context, recv, args, block);
}
@JRubyMethod(optional = 4, meta = true)
public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
RubyClass klass = (RubyClass) recv;
Tempfile tempfile = (Tempfile) klass.newInstance(context, args, block);
if (block.isGiven()) {
try {
return block.yield(context, tempfile);
} finally {
if (!tempfile.isClosed()) tempfile.close();
}
} else {
return tempfile;
}
}
@JRubyMethod
public RubyString inspect(ThreadContext context) {
return super.inspect(context);
}
@Override
public void finalize() throws Throwable {
try {
super.finalize();
} finally {
tmpFile.delete();
}
}
@Deprecated
public IRubyObject initialize19(IRubyObject[] args, Block block) {
return initialize(getRuntime().getCurrentContext(), args, block);
}
@Deprecated
public IRubyObject size19(ThreadContext context) {
return size(context);
}
}