From: <pj...@us...> - 2009-09-14 01:05:42
|
Revision: 6796 http://jython.svn.sourceforge.net/jython/?rev=6796&view=rev Author: pjenvey Date: 2009-09-14 01:05:23 +0000 (Mon, 14 Sep 2009) Log Message: ----------- force real O_APPEND for files' 'a' mode by going through FileOutputStream, otherwise emulate it with a performance hit for 'a+' mode fixes #1466 Modified Paths: -------------- trunk/jython/NEWS trunk/jython/src/org/python/core/io/FileIO.java Added Paths: ----------- trunk/jython/Lib/test/test_file_jy.py Added: trunk/jython/Lib/test/test_file_jy.py =================================================================== --- trunk/jython/Lib/test/test_file_jy.py (rev 0) +++ trunk/jython/Lib/test/test_file_jy.py 2009-09-14 01:05:23 UTC (rev 6796) @@ -0,0 +1,39 @@ +"""Misc file tests. + +Made for Jython. +""" +from __future__ import with_statement +import os +import unittest +from test import test_support + +class FileTestCase(unittest.TestCase): + + def tearDown(self): + if os.path.exists(test_support.TESTFN): + os.remove(test_support.TESTFN) + + def test_append(self): + self._test_append('ab') + + def test_appendplus(self): + self._test_append('a+') + + def _test_append(self, mode): + # http://bugs.jython.org/issue1466 + fp1 = open(test_support.TESTFN, 'ab') + fp1.write('test1\n') + fp2 = open(test_support.TESTFN, 'ab') + fp2.write('test2\n') + fp1.close() + fp2.close() + with open(test_support.TESTFN) as fp: + self.assertEqual('test1\ntest2\n', fp.read()) + + +def test_main(): + test_support.run_unittest(FileTestCase) + + +if __name__ == '__main__': + test_main() Modified: trunk/jython/NEWS =================================================================== --- trunk/jython/NEWS 2009-09-13 23:30:43 UTC (rev 6795) +++ trunk/jython/NEWS 2009-09-14 01:05:23 UTC (rev 6796) @@ -1,5 +1,9 @@ Jython NEWS +Jython 2.5.1rc3 + Bugs Fixed + - [ 1466 ] wrong handling of append only files + Jython 2.5.1rc2 New Features - zxJDBC supports the with-statement: connections are committed or rollbacked; cursors are closed Modified: trunk/jython/src/org/python/core/io/FileIO.java =================================================================== --- trunk/jython/src/org/python/core/io/FileIO.java 2009-09-13 23:30:43 UTC (rev 6795) +++ trunk/jython/src/org/python/core/io/FileIO.java 2009-09-14 01:05:23 UTC (rev 6796) @@ -3,6 +3,7 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -28,21 +29,27 @@ /** The underlying file channel */ private FileChannel fileChannel; - /** The underlying file (if known) */ + /** The underlying RandomAccessFile, if known. May be null */ private RandomAccessFile file; + /** The underlying FileOutputStream, if known. May be null */ + private FileOutputStream fileOutputStream; + /** true if the file is opened for reading ('r') */ - private boolean reading = false; + private boolean reading; /** true if the file is opened for writing ('w', 'a', or '+') */ - private boolean writing = false; + private boolean writing; /** true if the file is in appending mode ('a') */ - private boolean appending = false; + private boolean appending; /** true if the file is opened for reading and writing ('+') */ - private boolean plus = false; + private boolean plus; + /** true if write will emulate O_APPEND mode */ + private boolean emulateAppend; + /** * Construct a FileIO instance for the specified file name. * @@ -55,21 +62,21 @@ */ public FileIO(String name, String mode) { parseMode(mode); + File absPath = new RelativeFile(name); - File fullPath = new RelativeFile(name); - String rafMode = "r" + (writing ? "w" : ""); try { - if (plus && reading && !fullPath.isFile()) { - writing = false; // suppress "permission denied" - throw new FileNotFoundException(""); + if (appending && !reading) { + // Take advantage of FileOutputStream's append mode + fromFileOutputStream(absPath); + } else { + fromRandomAccessFile(absPath); + emulateAppend = appending; } - file = new RandomAccessFile(fullPath, rafMode); - fileChannel = file.getChannel(); } catch (FileNotFoundException fnfe) { - if (fullPath.isDirectory()) { + if (absPath.isDirectory()) { throw Py.IOError(Errno.EISDIR, name); } - if ((writing && !fullPath.canWrite()) + if ((writing && !absPath.canWrite()) || fnfe.getMessage().endsWith("(Permission denied)")) { throw Py.IOError(Errno.EACCES, name); } @@ -144,6 +151,34 @@ } /** + * Open the underlying FileChannel from a RandomAccessFile. + * + * @param absPath The absolute path File to open + */ + private void fromRandomAccessFile(File absPath) throws FileNotFoundException { + String rafMode = "r" + (writing ? "w" : ""); + if (plus && reading && !absPath.isFile()) { + // suppress "permission denied" + writing = false; + throw new FileNotFoundException(""); + } + file = new RandomAccessFile(absPath, rafMode); + fileChannel = file.getChannel(); + } + + /** + * Open the underlying FileChannel from a FileOutputStream in append mode, as opposed + * to a RandomAccessFile, for the use of the OS's underlying O_APPEND mode. This can + * only be used by 'a' (not 'a+') mode. + * + * @param absPath The absolute path File to open + */ + private void fromFileOutputStream(File absPath) throws FileNotFoundException { + fileOutputStream = new FileOutputStream(absPath, true); + fileChannel = fileOutputStream.getChannel(); + } + + /** * Raise a value error due to a mode string not containing exactly * one r/w/a/+ character. * @@ -183,11 +218,11 @@ @Override public boolean isatty() { checkClosed(); - if (file == null) { + if (file == null || fileOutputStream == null) { return false; } try { - return FileUtil.isatty(file.getFD()); + return FileUtil.isatty(file != null ? file.getFD() : fileOutputStream.getFD()); } catch (IOException e) { return false; } @@ -259,7 +294,8 @@ checkClosed(); checkWritable(); try { - return fileChannel.write(buf); + return !emulateAppend ? fileChannel.write(buf) : + fileChannel.write(buf, fileChannel.position()); } catch (IOException ioe) { throw Py.IOError(ioe); } @@ -277,12 +313,33 @@ checkClosed(); checkWritable(); try { - return fileChannel.write(bufs); + return !emulateAppend ? fileChannel.write(bufs) : writeAppend(bufs); } catch (IOException ioe) { throw Py.IOError(ioe); } } + /** + * Write multiple ByteBuffers while emulating O_APPEND mode. + * + * @param bufs an array of ByteBuffers + * @return the number of bytes written as a long + */ + private long writeAppend(ByteBuffer[] bufs) throws IOException { + long count = 0; + int bufCount; + for (ByteBuffer buf : bufs) { + if (!buf.hasRemaining()) { + continue; + } + if ((bufCount = fileChannel.write(buf, fileChannel.position())) == 0) { + break; + } + count += bufCount; + } + return count; + } + @Override public long seek(long pos, int whence) { checkClosed(); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |