| 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); } } |