00001 /******************************************************************************* 00002 00003 @file FileConduit.d 00004 00005 Copyright (c) 2004 Kris Bell 00006 00007 This software is provided 'as-is', without any express or implied 00008 warranty. In no event will the authors be held liable for damages 00009 of any kind arising from the use of this software. 00010 00011 Permission is hereby granted to anyone to use this software for any 00012 purpose, including commercial applications, and to alter it and/or 00013 redistribute it freely, subject to the following restrictions: 00014 00015 1. The origin of this software must not be misrepresented; you must 00016 not claim that you wrote the original software. If you use this 00017 software in a product, an acknowledgment within documentation of 00018 said product would be appreciated but is not required. 00019 00020 2. Altered source versions must be plainly marked as such, and must 00021 not be misrepresented as being the original software. 00022 00023 3. This notice may not be removed or altered from any distribution 00024 of the source. 00025 00026 4. Derivative works are permitted, but they must carry this notice 00027 in full and credit the original source. 00028 00029 00030 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 00031 00032 00033 @version Initial version; March 2004 00034 00035 @author Kris 00036 John Reimer 00037 Anders F Bjorklund (Darwin patches) 00038 Chris Sauls (Win95 file support) 00039 00040 *******************************************************************************/ 00041 00042 module mango.io.FileConduit; 00043 00044 public import mango.io.FilePath, 00045 mango.io.FileProxy; 00046 00047 private import mango.sys.OS; 00048 00049 private import mango.io.Buffer, 00050 mango.io.Conduit, 00051 mango.io.DeviceConduit; 00052 00053 /******************************************************************************* 00054 00055 Other O/S functions 00056 00057 *******************************************************************************/ 00058 00059 version (Win32) 00060 private extern (Windows) BOOL SetEndOfFile (HANDLE); 00061 else 00062 private extern (C) int ftruncate (int, int); 00063 00064 00065 /******************************************************************************* 00066 00067 Defines how a file should be opened. You can use the predefined 00068 instances, or create specializations for your own needs. 00069 00070 *******************************************************************************/ 00071 00072 struct FileStyle 00073 { 00074 /*********************************************************************** 00075 00076 Fits into 32 bits ... 00077 00078 ***********************************************************************/ 00079 00080 align(1): 00081 00082 struct Bits 00083 { 00084 ConduitStyle.Bits conduit; // access rights 00085 Open open; // how to open 00086 Share share; // how to share 00087 Cache cache; // how to cache 00088 } 00089 00090 /*********************************************************************** 00091 00092 ***********************************************************************/ 00093 00094 enum Open : ubyte { 00095 Exists=0, // must exist 00096 Create, // create always 00097 Truncate, // must exist 00098 Append, // create if necessary 00099 }; 00100 00101 /*********************************************************************** 00102 00103 ***********************************************************************/ 00104 00105 enum Share : ubyte { 00106 Read=0, // shared reading 00107 Write, // shared writing 00108 ReadWrite, // both 00109 }; 00110 00111 /*********************************************************************** 00112 00113 ***********************************************************************/ 00114 00115 enum Cache : ubyte { 00116 None = 0x00, // don't optimize 00117 Random = 0x01, // optimize for random 00118 Stream = 0x02, // optimize for stream 00119 WriteThru = 0x04, // backing-cache flag 00120 }; 00121 00122 /*********************************************************************** 00123 00124 ***********************************************************************/ 00125 00126 const Bits ReadExisting = {ConduitStyle.Read, Open.Exists}; 00127 const Bits WriteTruncate = {ConduitStyle.Write, Open.Truncate}; 00128 const Bits WriteAppending = {ConduitStyle.Write, Open.Append}; 00129 const Bits ReadWriteCreate = {ConduitStyle.ReadWrite, Open.Create}; 00130 const Bits ReadWriteExisting = {ConduitStyle.ReadWrite, Open.Exists}; 00131 00132 const Bits ReadExistingText = {ConduitStyle.ReadText, Open.Exists}; 00133 const Bits WriteTruncateText = {ConduitStyle.WriteText, Open.Truncate}; 00134 const Bits WriteAppendingText = {ConduitStyle.WriteText, Open.Append}; 00135 const Bits ReadWriteCreateText = {ConduitStyle.ReadWriteText, Open.Create}; 00136 const Bits ReadWriteExistingText = {ConduitStyle.ReadWriteText, Open.Exists}; 00137 } 00138 00139 00140 00141 /******************************************************************************* 00142 00143 Implements a means of reading and writing a generic file. Conduits 00144 are the primary means of accessing external data, and are usually 00145 routed through a Buffer. File conduit extends the generic conduit 00146 by providing file-specific methods to set the file size, seek to a 00147 specific file position, and so on. Also provided is a class for 00148 creating a memory-mapped Buffer upon a file. 00149 00150 Serial input and output is straightforward. In this example we 00151 copy a file directly to the console: 00152 00153 @code 00154 // open a file for reading 00155 FileConduit from = new FileConduit ("test.txt"); 00156 00157 // stream directly to console 00158 Stdout.conduit.copy (from); 00159 @endcode 00160 00161 And here we copy one file to another: 00162 00163 @code 00164 // open a file for reading 00165 FileConduit from = new FileConduit ("test.txt"); 00166 00167 // open another for writing 00168 FileConduit to = new FileConduit ("copy.txt", FileStyle.WriteTruncate); 00169 00170 // copy file 00171 to.copy (from); 00172 @endcode 00173 00174 FileConduit can just as easily handle random IO. Here we see how 00175 a Reader and Writer are used to perform simple input and output: 00176 00177 @code 00178 // open a file for reading 00179 FileConduit fc = new FileConduit ("random.bin", FileStyle.ReadWriteCreate); 00180 00181 // construct (binary) reader & writer upon this conduit 00182 Reader read = new Reader (fc); 00183 Writer write = new Writer (fc); 00184 00185 int x=10, y=20; 00186 00187 // write some data, and flush output since IO is buffered 00188 write (x) (y) (); 00189 00190 // rewind to file start 00191 fc.seek (0); 00192 00193 // read data back again, but swap destinations 00194 read (y) (x); 00195 00196 assert (y==10); 00197 assert (x==20); 00198 00199 fc.close(); 00200 @endcode 00201 00202 FileConduits can also be used directly, without Readers, Writers, or 00203 Buffers. To load a file directly into local-memory one might do this: 00204 00205 @code 00206 // open file for reading 00207 FileConduit fc = new FileConduit ("test.txt"); 00208 00209 // create an array to house the entire file 00210 char[] content = new char[fc.length]; 00211 00212 // read the file content. Return value is the number of bytes read 00213 int bytesRead = fc.read (content); 00214 @endcode 00215 00216 Conversely, one may write directly to a FileConduit, like so: 00217 00218 @code 00219 // open file for writing 00220 FileConduit to = new FileConduit ("text.txt", FileStyle.WriteTruncate); 00221 00222 // write an array of content to it 00223 int bytesWritten = fc.write (content); 00224 @endcode 00225 00226 00227 See File, FilePath, FileProxy, FileConst, FileScan, and FileSystem for 00228 additional functionality related to file manipulation. 00229 00230 Doxygen has a hard time with D version() statements, so part of this 00231 class is documented within FileConduit::VersionWin32 instead. 00232 00233 Compile with -version=Win32SansUnicode to enable Win95 & Win32s file 00234 support. 00235 00236 *******************************************************************************/ 00237 00238 class FileConduit : DeviceConduit, ISeekable 00239 { 00240 // the file we're working with 00241 private FilePath path; 00242 00243 // expose deviceconduit.copy() methods also 00244 alias DeviceConduit.copy copy; 00245 alias DeviceConduit.read read; 00246 alias DeviceConduit.write write; 00247 00248 /*********************************************************************** 00249 00250 Create a FileConduit with the provided path and style. 00251 00252 ***********************************************************************/ 00253 00254 this (char[] name, FileStyle.Bits style = FileStyle.ReadExisting) 00255 { 00256 this (new FilePath(name), style); 00257 } 00258 00259 /*********************************************************************** 00260 00261 Create a FileConduit from the provided proxy and style. 00262 00263 ***********************************************************************/ 00264 00265 this (FileProxy proxy, FileStyle.Bits style = FileStyle.ReadExisting) 00266 { 00267 this (proxy.getPath(), style); 00268 } 00269 00270 /*********************************************************************** 00271 00272 Create a FileConduit with the provided path and style. 00273 00274 ***********************************************************************/ 00275 00276 this (FilePath path, FileStyle.Bits style = FileStyle.ReadExisting) 00277 { 00278 // say we're seekable 00279 super (style.conduit, true); 00280 00281 // remember who we are 00282 this.path = path; 00283 00284 // open the file 00285 open (style); 00286 } 00287 00288 /*********************************************************************** 00289 00290 Return the FilePath used by this file. 00291 00292 ***********************************************************************/ 00293 00294 FilePath getPath () 00295 { 00296 return path; 00297 } 00298 00299 /*********************************************************************** 00300 00301 Return the current file position. 00302 00303 ***********************************************************************/ 00304 00305 ulong getPosition () 00306 { 00307 return seek (0, SeekAnchor.Current); 00308 } 00309 00310 /*********************************************************************** 00311 00312 Return the total length of this file. 00313 00314 ***********************************************************************/ 00315 00316 ulong length () 00317 { 00318 ulong pos, 00319 ret; 00320 00321 pos = getPosition (); 00322 ret = seek (0, SeekAnchor.End); 00323 seek (pos); 00324 return ret; 00325 } 00326 00327 /*********************************************************************** 00328 00329 Transfer the content of another file to this one. Returns a 00330 reference to this class on success, or throws an IOException 00331 upon failure. 00332 00333 ***********************************************************************/ 00334 00335 FileConduit copy (FilePath source) 00336 { 00337 auto fc = new FileConduit (source); 00338 try { 00339 super.copy (fc); 00340 } finally { 00341 fc.close (); 00342 } 00343 return this; 00344 } 00345 00346 /*********************************************************************** 00347 00348 Return the name used by this file. 00349 00350 ***********************************************************************/ 00351 00352 protected override char[] getName () 00353 { 00354 return path.toString; 00355 } 00356 00357 00358 /*********************************************************************** 00359 00360 Windows-specific code 00361 00362 ***********************************************************************/ 00363 00364 version(Win32) 00365 { 00366 private bool appending; 00367 00368 /*************************************************************** 00369 00370 Open a file with the provided style. 00371 00372 ***************************************************************/ 00373 00374 protected void open (FileStyle.Bits style) 00375 { 00376 DWORD attr, 00377 share, 00378 access, 00379 create; 00380 00381 alias DWORD[] Flags; 00382 00383 static const Flags Access = 00384 [ 00385 0, // invalid 00386 GENERIC_READ, 00387 GENERIC_WRITE, 00388 GENERIC_READ | GENERIC_WRITE, 00389 ]; 00390 00391 static const Flags Create = 00392 [ 00393 OPEN_EXISTING, // must exist 00394 CREATE_ALWAYS, // create always 00395 TRUNCATE_EXISTING, // must exist 00396 CREATE_ALWAYS, // (for appending) 00397 ]; 00398 00399 static const Flags Share = 00400 [ 00401 FILE_SHARE_READ, 00402 FILE_SHARE_WRITE, 00403 FILE_SHARE_READ | FILE_SHARE_WRITE, 00404 ]; 00405 00406 static const Flags Attr = 00407 [ 00408 0, 00409 FILE_FLAG_RANDOM_ACCESS, 00410 FILE_FLAG_SEQUENTIAL_SCAN, 00411 0, 00412 FILE_FLAG_WRITE_THROUGH, 00413 ]; 00414 00415 attr = Attr[style.cache]; 00416 share = Share[style.share]; 00417 create = Create[style.open]; 00418 access = Access[style.conduit.access & ConduitStyle.Access.Mask]; 00419 00420 version (Win32SansUnicode) 00421 handle = CreateFileA (path.toUtf8, access, share, 00422 null, create, 00423 attr | FILE_ATTRIBUTE_NORMAL, 00424 cast(HANDLE) null); 00425 else 00426 handle = CreateFileW (path.toUtf16, access, share, 00427 null, create, 00428 attr | FILE_ATTRIBUTE_NORMAL, 00429 cast(HANDLE) null); 00430 00431 if (handle == INVALID_HANDLE_VALUE) 00432 error (); 00433 00434 // move to end of file? 00435 if (style.open == FileStyle.Open.Append) 00436 appending = true; 00437 } 00438 00439 /*************************************************************** 00440 00441 Write a chunk of bytes to the file from the provided 00442 array (typically that belonging to an IBuffer) 00443 00444 ***************************************************************/ 00445 00446 protected uint writer (void[] src) 00447 { 00448 DWORD written; 00449 00450 // try to emulate the Unix O_APPEND mode 00451 if (appending) 00452 SetFilePointer (handle, 0, null, SeekAnchor.End); 00453 00454 return super.writer (src); 00455 } 00456 00457 /*************************************************************** 00458 00459 Set the file size to be that of the current seek 00460 position. The file must be writable for this to 00461 succeed. 00462 00463 ***************************************************************/ 00464 00465 void truncate () 00466 { 00467 // must have Generic_Write access 00468 if (! SetEndOfFile (handle)) 00469 error (); 00470 } 00471 00472 /*************************************************************** 00473 00474 Set the file seek position to the specified offset 00475 from the given anchor. 00476 00477 ***************************************************************/ 00478 00479 ulong seek (ulong offset, SeekAnchor anchor = SeekAnchor.Begin) 00480 { 00481 LONG high = cast(LONG) (offset >> 32); 00482 ulong result = SetFilePointer (handle, cast(LONG) offset, 00483 &high, anchor); 00484 00485 if (result == -1 && 00486 GetLastError() != ERROR_SUCCESS) 00487 error (); 00488 00489 return result + (cast(ulong) high << 32); 00490 } 00491 } 00492 00493 00494 /*********************************************************************** 00495 00496 Unix-specific code. Note that some methods are 32bit only 00497 00498 ***********************************************************************/ 00499 00500 version (Posix) 00501 { 00502 /*************************************************************** 00503 00504 Open a file with the provided style. 00505 00506 ***************************************************************/ 00507 00508 protected void open (FileStyle.Bits style) 00509 { 00510 int share, 00511 access; 00512 00513 alias int[] Flags; 00514 00515 static const Flags Access = 00516 [ 00517 0, // invalid 00518 O_RDONLY, 00519 O_WRONLY, 00520 O_RDWR, 00521 ]; 00522 00523 static const Flags Create = 00524 [ 00525 0, // open existing 00526 O_CREAT, // create always 00527 O_TRUNC, // must exist 00528 O_APPEND | O_CREAT, 00529 ]; 00530 00531 // this is not the same as Windows sharing, 00532 // but it's perhaps a reasonable approximation 00533 static const Flags Share = 00534 [ 00535 0640, // read access 00536 0620, // write access 00537 0660, // read & write 00538 ]; 00539 00540 share = Share[style.share]; 00541 access = Access[style.conduit.access & ConduitStyle.Access.Mask] | Create[style.open]; 00542 00543 handle = posix.open (path.toUtf8, access, share); 00544 if (handle == -1) 00545 error (); 00546 } 00547 00548 /*************************************************************** 00549 00550 32bit only ... 00551 00552 Set the file size to be that of the current seek 00553 position. The file must be writable for this to 00554 succeed. 00555 00556 ***************************************************************/ 00557 00558 void truncate () 00559 { 00560 // set filesize to be current seek-position 00561 if (ftruncate (handle, getPosition()) == -1) 00562 error (); 00563 } 00564 00565 /*************************************************************** 00566 00567 32bit only ... 00568 00569 Set the file seek position to the specified offset 00570 from the given anchor. 00571 00572 ***************************************************************/ 00573 00574 ulong seek (ulong offset, SeekAnchor anchor = SeekAnchor.Begin) 00575 { 00576 uint result = posix.lseek (handle, offset, anchor); 00577 if (result == -1) 00578 error (); 00579 return result; 00580 } 00581 } 00582 } 00583