123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 |
|
/*******************************************************************************
copyright: Copyright (c) 2004 Kris Bell. All rights reserved
license: BSD style: $(LICENSE)
version: Mar 2004: Initial release
Dec 2006: Outback release
Nov 2008: relocated and simplified
author: Kris,
John Reimer,
Anders F Bjorklund (Darwin patches),
Chris Sauls (Win95 file support)
*******************************************************************************/
module tango.io.device.File;
private import tango.sys.Common;
private import tango.io.device.Device;
private import stdc = tango.stdc.stringz;
/*******************************************************************************
platform-specific functions
*******************************************************************************/
version (Win32)
private import Utf = tango.text.convert.Utf;
else
private import tango.stdc.posix.unistd;
/*******************************************************************************
Implements a means of reading and writing a generic file. Conduits
are the primary means of accessing external data, and File
extends the basic pattern by providing file-specific methods to
set the file size, seek to a specific file position and so on.
Serial input and output is straightforward. In this example we
copy a file directly to the console:
---
// open a file for reading
auto from = new File ("test.txt");
// stream directly to console
Stdout.copy (from);
---
And here we copy one file to another:
---
// open file for reading
auto from = new File ("test.txt");
// open another for writing
auto to = new File ("copy.txt", File.WriteCreate);
// copy file and close
to.copy.close;
from.close;
---
You can use InputStream.load() to load a file directly into memory:
---
auto file = new File ("test.txt");
auto content = file.load;
file.close;
---
Or use a convenience static function within File:
---
auto content = File.get ("test.txt");
---
A more explicit version with a similar result would be:
---
// open file for reading
auto file = new File ("test.txt");
// create an array to house the entire file
auto content = new char [file.length];
// read the file content. Return value is the number of bytes read
auto bytes = file.read (content);
file.close;
---
Conversely, one may write directly to a File like so:
---
// open file for writing
auto to = new File ("text.txt", File.WriteCreate);
// write an array of content to it
auto bytes = to.write (content);
---
There are equivalent static functions, File.set() and
File.append(), which set or append file content respectively
File can happily handle random I/O. Here we use seek() to
relocate the file pointer:
---
// open a file for reading and writing
auto file = new File ("random.bin", File.ReadWriteCreate);
// write some data
file.write ("testing");
// rewind to file start
file.seek (0);
// read data back again
char[10] tmp;
auto bytes = file.read (tmp);
file.close;
---
Note that File is unbuffered by default - wrap an instance within
tango.io.stream.Buffered for buffered I/O.
Compile with -version=Win32SansUnicode to enable Win95 & Win32s file
support.
*******************************************************************************/
class File : Device, Device.Seek, Device.Truncate
{
public alias Device.read read;
public alias Device.write write;
/***********************************************************************
Fits into 32 bits ...
***********************************************************************/
align(1) struct Style
{
Access access; /// access rights
Open open; /// how to open
Share share; /// how to share
Cache cache; /// how to cache
}
/***********************************************************************
***********************************************************************/
enum Access : ubyte {
Read = 0x01, /// is readable
Write = 0x02, /// is writable
ReadWrite = 0x03, /// both
}
/***********************************************************************
***********************************************************************/
enum Open : ubyte {
Exists=0, /// must exist
Create, /// create or truncate
Sedate, /// create if necessary
Append, /// create if necessary
New, /// can't exist
};
/***********************************************************************
***********************************************************************/
enum Share : ubyte {
None=0, /// no sharing
Read, /// shared reading
ReadWrite, /// open for anything
};
/***********************************************************************
***********************************************************************/
enum Cache : ubyte {
None = 0x00, /// don't optimize
Random = 0x01, /// optimize for random
Stream = 0x02, /// optimize for stream
WriteThru = 0x04, /// backing-cache flag
};
/***********************************************************************
Read an existing file
***********************************************************************/
const Style ReadExisting = {Access.Read, Open.Exists};
/***********************************************************************
Read an existing file
***********************************************************************/
const Style ReadShared = {Access.Read, Open.Exists, Share.Read};
/***********************************************************************
Write on an existing file. Do not create
***********************************************************************/
const Style WriteExisting = {Access.Write, Open.Exists};
/***********************************************************************
Write on a clean file. Create if necessary
***********************************************************************/
const Style WriteCreate = {Access.Write, Open.Create};
/***********************************************************************
Write at the end of the file
***********************************************************************/
const Style WriteAppending = {Access.Write, Open.Append};
/***********************************************************************
Read and write an existing file
***********************************************************************/
const Style ReadWriteExisting = {Access.ReadWrite, Open.Exists};
/***********************************************************************
Read & write on a clean file. Create if necessary
***********************************************************************/
const Style ReadWriteCreate = {Access.ReadWrite, Open.Create};
/***********************************************************************
Read and Write. Use existing file if present
***********************************************************************/
const Style ReadWriteOpen = {Access.ReadWrite, Open.Sedate};
// the file we're working with
private char[] path_;
// the style we're opened with
private Style style_;
/***********************************************************************
Create a File for use with open()
Note that File is unbuffered by default - wrap an instance
within tango.io.stream.Buffered for buffered I/O
***********************************************************************/
this ()
{
}
/***********************************************************************
Create a File with the provided path and style.
Note that File is unbuffered by default - wrap an instance
within tango.io.stream.Buffered for buffered I/O
***********************************************************************/
this (char[] path, Style style = ReadExisting)
{
open (path, style);
}
/***********************************************************************
Return the Style used for this file.
***********************************************************************/
Style style ()
{
return style_;
}
/***********************************************************************
Return the path used by this file.
***********************************************************************/
override char[] toString ()
{
return path_;
}
/***********************************************************************
Convenience function to return the content of a file.
Returns a slice of the provided output buffer, where
that has sufficient capacity, and allocates from the
heap where the file content is larger.
Content size is determined via the file-system, per
File.length, although that may be misleading for some
*nix systems. An alternative is to use File.load which
loads content until an Eof is encountered
***********************************************************************/
static void[] get (char[] path, void[] dst = null)
{
scope file = new File (path);
// allocate enough space for the entire file
auto len = cast(size_t) file.length;
if (dst.length < len)
dst.length = len;
//read the content
len = file.read (dst);
if (len is file.Eof)
file.error ("File.read :: unexpected eof");
return dst [0 .. len];
}
/***********************************************************************
Convenience function to set file content and length to
reflect the given array
***********************************************************************/
static void set (char[] path, void[] content)
{
scope file = new File (path, ReadWriteCreate);
file.write (content);
}
/***********************************************************************
Convenience function to append content to a file
***********************************************************************/
static void append (char[] path, void[] content)
{
scope file = new File (path, WriteAppending);
file.write (content);
}
/***********************************************************************
Windows-specific code
***********************************************************************/
version(Win32)
{
/***************************************************************
Low level open for sub-classes that need to apply specific
attributes.
Return:
false in case of failure
***************************************************************/
protected bool open (char[] path, Style style, DWORD addattr)
{
DWORD attr,
share,
access,
create;
alias DWORD[] Flags;
static const Flags Access =
[
0, // invalid
GENERIC_READ,
GENERIC_WRITE,
GENERIC_READ | GENERIC_WRITE,
];
static const Flags Create =
[
OPEN_EXISTING, // must exist
CREATE_ALWAYS, // truncate always
OPEN_ALWAYS, // create if needed
OPEN_ALWAYS, // (for appending)
CREATE_NEW // can't exist
];
static const Flags Share =
[
0,
FILE_SHARE_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
];
static const Flags Attr =
[
0,
FILE_FLAG_RANDOM_ACCESS,
FILE_FLAG_SEQUENTIAL_SCAN,
0,
FILE_FLAG_WRITE_THROUGH,
];
// remember our settings
assert(path);
path_ = path;
style_ = style;
attr = Attr[style.cache] | addattr;
share = Share[style.share];
create = Create[style.open];
access = Access[style.access];
if (scheduler)
attr |= FILE_FLAG_OVERLAPPED;// + FILE_FLAG_NO_BUFFERING;
// zero terminate the path
char[512] zero = void;
auto name = stdc.toStringz (path, zero);
version (Win32SansUnicode)
io.handle = CreateFileA (name, access, share,
null, create,
attr | FILE_ATTRIBUTE_NORMAL,
null);
else
{
// convert to utf16
wchar[512] convert = void;
auto wide = Utf.toString16 (name[0..path.length+1], convert);
// open the file
io.handle = CreateFileW (wide.ptr, access, share,
null, create,
attr | FILE_ATTRIBUTE_NORMAL,
null);
}
if (io.handle is INVALID_HANDLE_VALUE)
return false;
// reset extended error
SetLastError (ERROR_SUCCESS);
// move to end of file?
if (style.open is Open.Append)
*(cast(long*) &io.asynch.Offset) = -1;
else
io.track = true;
// monitor this handle for async I/O?
if (scheduler)
scheduler.open (io.handle, toString);
return true;
}
/***************************************************************
Open a file with the provided style.
***************************************************************/
void open (char[] path, Style style = ReadExisting)
{
if (! open (path, style, 0))
error;
}
/***************************************************************
Set the file size to be that of the current seek
position. The file must be writable for this to
succeed.
***************************************************************/
void truncate ()
{
truncate (position);
}
/***************************************************************
Set the file size to be the specified length. The
file must be writable for this to succeed.
***************************************************************/
override void truncate (long size)
{
auto s = seek (size);
assert (s is size);
// must have Generic_Write access
if (! SetEndOfFile (io.handle))
error;
}
/***************************************************************
Set the file seek position to the specified offset
from the given anchor.
***************************************************************/
override long seek (long offset, Anchor anchor = Anchor.Begin)
{
long newOffset;
// hack to ensure overlapped.Offset and file location
// are correctly in synch ...
if (anchor is Anchor.Current)
SetFilePointerEx (io.handle,
*cast(LARGE_INTEGER*) &io.asynch.Offset,
cast(PLARGE_INTEGER) &newOffset, 0);
if (! SetFilePointerEx (io.handle, *cast(LARGE_INTEGER*)
&offset, cast(PLARGE_INTEGER)
&newOffset, anchor))
error;
return (*cast(long*) &io.asynch.Offset) = newOffset;
}
/***************************************************************
Return the current file position.
***************************************************************/
long position ()
{
return *cast(long*) &io.asynch.Offset;
}
/***************************************************************
Return the total length of this file.
***************************************************************/
long length ()
{
long len;
if (! GetFileSizeEx (io.handle, cast(PLARGE_INTEGER) &len))
error;
return len;
}
}
/***********************************************************************
Unix-specific code. Note that some methods are 32bit only
***********************************************************************/
version (Posix)
{
/***************************************************************
Low level open for sub-classes that need to apply specific
attributes.
Return:
false in case of failure
***************************************************************/
protected bool open (char[] path, Style style,
int addflags, int access = 0666)
{
alias int[] Flags;
const O_LARGEFILE = 0x8000;
static const Flags Access =
[
0, // invalid
O_RDONLY,
O_WRONLY,
O_RDWR,
];
static const Flags Create =
[
0, // open existing
O_CREAT | O_TRUNC, // truncate always
O_CREAT, // create if needed
O_APPEND | O_CREAT, // append
O_CREAT | O_EXCL, // can't exist
];
static const short[] Locks =
[
F_WRLCK, // no sharing
F_RDLCK, // shared read
];
// remember our settings
assert(path);
path_ = path;
style_ = style;
// zero terminate and convert to utf16
char[512] zero = void;
auto name = stdc.toStringz (path, zero);
auto mode = Access[style.access] | Create[style.open];
// always open as a large file
handle = posix.open (name, mode | O_LARGEFILE | addflags,
access);
if (handle is -1)
return false;
return true;
}
/***************************************************************
Open a file with the provided style.
Note that files default to no-sharing. That is,
they are locked exclusively to the host process
unless otherwise stipulated. We do this in order
to expose the same default behaviour as Win32
NO FILE LOCKING FOR BORKED POSIX
***************************************************************/
void open (char[] path, Style style = ReadExisting)
{
if (! open (path, style, 0))
error;
}
/***************************************************************
Set the file size to be that of the current seek
position. The file must be writable for this to
succeed.
***************************************************************/
void truncate ()
{
truncate (position);
}
/***************************************************************
Set the file size to be the specified length. The
file must be writable for this to succeed.
***************************************************************/
override void truncate (long size)
{
// set filesize to be current seek-position
if (posix.ftruncate (handle, cast(off_t) size) is -1)
error;
}
/***************************************************************
Set the file seek position to the specified offset
from the given anchor.
***************************************************************/
override long seek (long offset, Anchor anchor = Anchor.Begin)
{
long result = posix.lseek (handle, cast(off_t) offset, anchor);
if (result is -1)
error;
return result;
}
/***************************************************************
Return the current file position.
***************************************************************/
long position ()
{
return seek (0, Anchor.Current);
}
/***************************************************************
Return the total length of this file.
***************************************************************/
long length ()
{
stat_t stats = void;
if (posix.fstat (handle, &stats))
error;
return cast(long) stats.st_size;
}
}
}
debug (File)
{
import tango.io.Stdout;
void main()
{
char[10] ff;
auto file = new File("file.d");
auto content = cast(char[]) file.load (file);
assert (content.length is file.length);
assert (file.read(ff) is file.Eof);
assert (file.position is content.length);
file.seek (0);
assert (file.position is 0);
assert (file.read(ff) is 10);
assert (file.position is 10);
assert (file.seek(0, file.Anchor.Current) is 10);
assert (file.seek(0, file.Anchor.Current) is 10);
}
}
|