7e2d1dd038
The library is able to support simple TCP servers in the current state. The API is still mostly compatible with mainline vibe.d, but the driver systen has been replaced by the eventcore library and sockets/files/timers/... are now structs with automatic reference counting instead of GC collected classes. The stream interfaces have been removed for now.
639 lines
15 KiB
D
639 lines
15 KiB
D
/**
|
|
File handling functions and types.
|
|
|
|
Copyright: © 2012-2016 RejectedSoftware e.K.
|
|
License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
|
|
Authors: Sönke Ludwig
|
|
*/
|
|
module vibe.core.file;
|
|
|
|
//public import vibe.core.stream;
|
|
//public import vibe.inet.url;
|
|
import vibe.core.path;
|
|
|
|
import core.stdc.stdio;
|
|
import core.sys.posix.unistd;
|
|
import core.sys.posix.fcntl;
|
|
import core.sys.posix.sys.stat;
|
|
import std.conv : octal;
|
|
import vibe.core.log;
|
|
import std.datetime;
|
|
import std.exception;
|
|
import std.file;
|
|
import std.path;
|
|
import std.string;
|
|
|
|
|
|
version(Posix){
|
|
private extern(C) int mkstemps(char* templ, int suffixlen);
|
|
}
|
|
|
|
|
|
/**
|
|
Opens a file stream with the specified mode.
|
|
*/
|
|
FileStream openFile(Path path, FileMode mode = FileMode.read)
|
|
{
|
|
assert(false);
|
|
//return eventDriver.openFile(path, mode);
|
|
}
|
|
/// ditto
|
|
FileStream openFile(string path, FileMode mode = FileMode.read)
|
|
{
|
|
return openFile(Path(path), mode);
|
|
}
|
|
|
|
|
|
/**
|
|
Read a whole file into a buffer.
|
|
|
|
If the supplied buffer is large enough, it will be used to store the
|
|
contents of the file. Otherwise, a new buffer will be allocated.
|
|
|
|
Params:
|
|
path = The path of the file to read
|
|
buffer = An optional buffer to use for storing the file contents
|
|
*/
|
|
ubyte[] readFile(Path path, ubyte[] buffer = null, size_t max_size = size_t.max)
|
|
{
|
|
auto fil = openFile(path);
|
|
scope (exit) fil.close();
|
|
enforce(fil.size <= max_size, "File is too big.");
|
|
auto sz = cast(size_t)fil.size;
|
|
auto ret = sz <= buffer.length ? buffer[0 .. sz] : new ubyte[sz];
|
|
fil.read(ret);
|
|
return ret;
|
|
}
|
|
/// ditto
|
|
ubyte[] readFile(string path, ubyte[] buffer = null, size_t max_size = size_t.max)
|
|
{
|
|
return readFile(Path(path), buffer, max_size);
|
|
}
|
|
|
|
|
|
/**
|
|
Write a whole file at once.
|
|
*/
|
|
void writeFile(Path path, in ubyte[] contents)
|
|
{
|
|
auto fil = openFile(path, FileMode.createTrunc);
|
|
scope (exit) fil.close();
|
|
fil.write(contents);
|
|
}
|
|
/// ditto
|
|
void writeFile(string path, in ubyte[] contents)
|
|
{
|
|
writeFile(Path(path), contents);
|
|
}
|
|
|
|
/**
|
|
Convenience function to append to a file.
|
|
*/
|
|
void appendToFile(Path path, string data) {
|
|
auto fil = openFile(path, FileMode.append);
|
|
scope(exit) fil.close();
|
|
fil.write(data);
|
|
}
|
|
/// ditto
|
|
void appendToFile(string path, string data)
|
|
{
|
|
appendToFile(Path(path), data);
|
|
}
|
|
|
|
/**
|
|
Read a whole UTF-8 encoded file into a string.
|
|
|
|
The resulting string will be sanitized and will have the
|
|
optional byte order mark (BOM) removed.
|
|
*/
|
|
string readFileUTF8(Path path)
|
|
{
|
|
import vibe.internal.string;
|
|
|
|
return stripUTF8Bom(sanitizeUTF8(readFile(path)));
|
|
}
|
|
/// ditto
|
|
string readFileUTF8(string path)
|
|
{
|
|
return readFileUTF8(Path(path));
|
|
}
|
|
|
|
|
|
/**
|
|
Write a string into a UTF-8 encoded file.
|
|
|
|
The file will have a byte order mark (BOM) prepended.
|
|
*/
|
|
void writeFileUTF8(Path path, string contents)
|
|
{
|
|
static immutable ubyte[] bom = [0xEF, 0xBB, 0xBF];
|
|
auto fil = openFile(path, FileMode.createTrunc);
|
|
scope (exit) fil.close();
|
|
fil.write(bom);
|
|
fil.write(contents);
|
|
}
|
|
|
|
/**
|
|
Creates and opens a temporary file for writing.
|
|
*/
|
|
FileStream createTempFile(string suffix = null)
|
|
{
|
|
version(Windows){
|
|
import std.conv : to;
|
|
char[L_tmpnam] tmp;
|
|
tmpnam(tmp.ptr);
|
|
auto tmpname = to!string(tmp.ptr);
|
|
if( tmpname.startsWith("\\") ) tmpname = tmpname[1 .. $];
|
|
tmpname ~= suffix;
|
|
return openFile(tmpname, FileMode.createTrunc);
|
|
} else {
|
|
enum pattern ="/tmp/vtmp.XXXXXX";
|
|
scope templ = new char[pattern.length+suffix.length+1];
|
|
templ[0 .. pattern.length] = pattern;
|
|
templ[pattern.length .. $-1] = (suffix)[];
|
|
templ[$-1] = '\0';
|
|
assert(suffix.length <= int.max);
|
|
auto fd = mkstemps(templ.ptr, cast(int)suffix.length);
|
|
enforce(fd >= 0, "Failed to create temporary file.");
|
|
assert(false);
|
|
//return eventDriver.adoptFile(fd, Path(templ[0 .. $-1].idup), FileMode.createTrunc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Moves or renames a file.
|
|
|
|
Params:
|
|
from = Path to the file/directory to move/rename.
|
|
to = The target path
|
|
copy_fallback = Determines if copy/remove should be used in case of the
|
|
source and destination path pointing to different devices.
|
|
*/
|
|
void moveFile(Path from, Path to, bool copy_fallback = false)
|
|
{
|
|
moveFile(from.toNativeString(), to.toNativeString(), copy_fallback);
|
|
}
|
|
/// ditto
|
|
void moveFile(string from, string to, bool copy_fallback = false)
|
|
{
|
|
if (!copy_fallback) {
|
|
std.file.rename(from, to);
|
|
} else {
|
|
try {
|
|
std.file.rename(from, to);
|
|
} catch (FileException e) {
|
|
std.file.copy(from, to);
|
|
std.file.remove(from);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Copies a file.
|
|
|
|
Note that attributes and time stamps are currently not retained.
|
|
|
|
Params:
|
|
from = Path of the source file
|
|
to = Path for the destination file
|
|
overwrite = If true, any file existing at the destination path will be
|
|
overwritten. If this is false, an exception will be thrown should
|
|
a file already exist at the destination path.
|
|
|
|
Throws:
|
|
An Exception if the copy operation fails for some reason.
|
|
*/
|
|
void copyFile(Path from, Path to, bool overwrite = false)
|
|
{
|
|
{
|
|
auto src = openFile(from, FileMode.read);
|
|
scope(exit) src.close();
|
|
enforce(overwrite || !existsFile(to), "Destination file already exists.");
|
|
auto dst = openFile(to, FileMode.createTrunc);
|
|
scope(exit) dst.close();
|
|
dst.write(src);
|
|
}
|
|
|
|
// TODO: retain attributes and time stamps
|
|
}
|
|
/// ditto
|
|
void copyFile(string from, string to)
|
|
{
|
|
copyFile(Path(from), Path(to));
|
|
}
|
|
|
|
/**
|
|
Removes a file
|
|
*/
|
|
void removeFile(Path path)
|
|
{
|
|
removeFile(path.toNativeString());
|
|
}
|
|
/// ditto
|
|
void removeFile(string path)
|
|
{
|
|
std.file.remove(path);
|
|
}
|
|
|
|
/**
|
|
Checks if a file exists
|
|
*/
|
|
bool existsFile(Path path) nothrow
|
|
{
|
|
return existsFile(path.toNativeString());
|
|
}
|
|
/// ditto
|
|
bool existsFile(string path) nothrow
|
|
{
|
|
// This was *annotated* nothrow in 2.067.
|
|
static if (__VERSION__ < 2067)
|
|
scope(failure) assert(0, "Error: existsFile should never throw");
|
|
return std.file.exists(path);
|
|
}
|
|
|
|
/** Stores information about the specified file/directory into 'info'
|
|
|
|
Throws: A `FileException` is thrown if the file does not exist.
|
|
*/
|
|
FileInfo getFileInfo(Path path)
|
|
{
|
|
auto ent = DirEntry(path.toNativeString());
|
|
return makeFileInfo(ent);
|
|
}
|
|
/// ditto
|
|
FileInfo getFileInfo(string path)
|
|
{
|
|
return getFileInfo(Path(path));
|
|
}
|
|
|
|
/**
|
|
Creates a new directory.
|
|
*/
|
|
void createDirectory(Path path)
|
|
{
|
|
mkdir(path.toNativeString());
|
|
}
|
|
/// ditto
|
|
void createDirectory(string path)
|
|
{
|
|
createDirectory(Path(path));
|
|
}
|
|
|
|
/**
|
|
Enumerates all files in the specified directory.
|
|
*/
|
|
void listDirectory(Path path, scope bool delegate(FileInfo info) del)
|
|
{
|
|
foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) )
|
|
if( !del(makeFileInfo(ent)) )
|
|
break;
|
|
}
|
|
/// ditto
|
|
void listDirectory(string path, scope bool delegate(FileInfo info) del)
|
|
{
|
|
listDirectory(Path(path), del);
|
|
}
|
|
/// ditto
|
|
int delegate(scope int delegate(ref FileInfo)) iterateDirectory(Path path)
|
|
{
|
|
int iterator(scope int delegate(ref FileInfo) del){
|
|
int ret = 0;
|
|
listDirectory(path, (fi){
|
|
ret = del(fi);
|
|
return ret == 0;
|
|
});
|
|
return ret;
|
|
}
|
|
return &iterator;
|
|
}
|
|
/// ditto
|
|
int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path)
|
|
{
|
|
return iterateDirectory(Path(path));
|
|
}
|
|
|
|
/**
|
|
Starts watching a directory for changes.
|
|
*/
|
|
DirectoryWatcher watchDirectory(Path path, bool recursive = true)
|
|
{
|
|
assert(false);
|
|
//return eventDriver.watchDirectory(path, recursive);
|
|
}
|
|
// ditto
|
|
DirectoryWatcher watchDirectory(string path, bool recursive = true)
|
|
{
|
|
return watchDirectory(Path(path), recursive);
|
|
}
|
|
|
|
/**
|
|
Returns the current working directory.
|
|
*/
|
|
Path getWorkingDirectory()
|
|
{
|
|
return Path(std.file.getcwd());
|
|
}
|
|
|
|
|
|
/** Contains general information about a file.
|
|
*/
|
|
struct FileInfo {
|
|
/// Name of the file (not including the path)
|
|
string name;
|
|
|
|
/// Size of the file (zero for directories)
|
|
ulong size;
|
|
|
|
/// Time of the last modification
|
|
SysTime timeModified;
|
|
|
|
/// Time of creation (not available on all operating systems/file systems)
|
|
SysTime timeCreated;
|
|
|
|
/// True if this is a symlink to an actual file
|
|
bool isSymlink;
|
|
|
|
/// True if this is a directory or a symlink pointing to a directory
|
|
bool isDirectory;
|
|
}
|
|
|
|
/**
|
|
Specifies how a file is manipulated on disk.
|
|
*/
|
|
enum FileMode {
|
|
/// The file is opened read-only.
|
|
read,
|
|
/// The file is opened for read-write random access.
|
|
readWrite,
|
|
/// The file is truncated if it exists or created otherwise and then opened for read-write access.
|
|
createTrunc,
|
|
/// The file is opened for appending data to it and created if it does not exist.
|
|
append
|
|
}
|
|
|
|
/**
|
|
Accesses the contents of a file as a stream.
|
|
*/
|
|
struct FileStream {
|
|
import std.algorithm.comparison : min;
|
|
import vibe.core.core : yield;
|
|
import core.stdc.errno;
|
|
|
|
version (Windows) {} else
|
|
{
|
|
enum O_BINARY = 0;
|
|
}
|
|
|
|
private {
|
|
int m_fileDescriptor;
|
|
Path m_path;
|
|
ulong m_size;
|
|
ulong m_ptr = 0;
|
|
FileMode m_mode;
|
|
bool m_ownFD = true;
|
|
}
|
|
|
|
this(Path path, FileMode mode)
|
|
{
|
|
auto pathstr = path.toNativeString();
|
|
final switch(mode){
|
|
case FileMode.read:
|
|
m_fileDescriptor = open(pathstr.toStringz(), O_RDONLY|O_BINARY);
|
|
break;
|
|
case FileMode.readWrite:
|
|
m_fileDescriptor = open(pathstr.toStringz(), O_RDWR|O_BINARY);
|
|
break;
|
|
case FileMode.createTrunc:
|
|
m_fileDescriptor = open(pathstr.toStringz(), O_RDWR|O_CREAT|O_TRUNC|O_BINARY, octal!644);
|
|
break;
|
|
case FileMode.append:
|
|
m_fileDescriptor = open(pathstr.toStringz(), O_WRONLY|O_CREAT|O_APPEND|O_BINARY, octal!644);
|
|
break;
|
|
}
|
|
if( m_fileDescriptor < 0 )
|
|
//throw new Exception(format("Failed to open '%s' with %s: %d", pathstr, cast(int)mode, errno));
|
|
throw new Exception("Failed to open file '"~pathstr~"'.");
|
|
|
|
this(m_fileDescriptor, path, mode);
|
|
}
|
|
|
|
this(int fd, Path path, FileMode mode)
|
|
{
|
|
assert(fd >= 0);
|
|
m_fileDescriptor = fd;
|
|
m_path = path;
|
|
m_mode = mode;
|
|
|
|
version(linux){
|
|
// stat_t seems to be defined wrong on linux/64
|
|
m_size = lseek(m_fileDescriptor, 0, SEEK_END);
|
|
} else {
|
|
stat_t st;
|
|
fstat(m_fileDescriptor, &st);
|
|
m_size = st.st_size;
|
|
|
|
// (at least) on windows, the created file is write protected
|
|
version(Windows){
|
|
if( mode == FileMode.createTrunc )
|
|
chmod(path.toNativeString().toStringz(), S_IREAD|S_IWRITE);
|
|
}
|
|
}
|
|
lseek(m_fileDescriptor, 0, SEEK_SET);
|
|
|
|
logDebug("opened file %s with %d bytes as %d", path.toNativeString(), m_size, m_fileDescriptor);
|
|
}
|
|
|
|
~this()
|
|
{
|
|
close();
|
|
}
|
|
|
|
@property int fd() { return m_fileDescriptor; }
|
|
|
|
/// The path of the file.
|
|
@property Path path() const { return m_path; }
|
|
|
|
/// Determines if the file stream is still open
|
|
@property bool isOpen() const { return m_fileDescriptor >= 0; }
|
|
@property ulong size() const { return m_size; }
|
|
@property bool readable() const { return m_mode != FileMode.append; }
|
|
@property bool writable() const { return m_mode != FileMode.read; }
|
|
|
|
void takeOwnershipOfFD()
|
|
{
|
|
enforce(m_ownFD);
|
|
m_ownFD = false;
|
|
}
|
|
|
|
void seek(ulong offset)
|
|
{
|
|
version (Win32) {
|
|
enforce(offset <= off_t.max, "Cannot seek above 4GB on Windows x32.");
|
|
auto pos = lseek(m_fileDescriptor, cast(off_t)offset, SEEK_SET);
|
|
} else auto pos = lseek(m_fileDescriptor, offset, SEEK_SET);
|
|
enforce(pos == offset, "Failed to seek in file.");
|
|
m_ptr = offset;
|
|
}
|
|
|
|
ulong tell() { return m_ptr; }
|
|
|
|
/// Closes the file handle.
|
|
void close()
|
|
{
|
|
if( m_fileDescriptor != -1 && m_ownFD ){
|
|
.close(m_fileDescriptor);
|
|
m_fileDescriptor = -1;
|
|
}
|
|
}
|
|
|
|
@property bool empty() const { assert(this.readable); return m_ptr >= m_size; }
|
|
@property ulong leastSize() const { assert(this.readable); return m_size - m_ptr; }
|
|
@property bool dataAvailableForRead() { return true; }
|
|
|
|
const(ubyte)[] peek()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
void read(ubyte[] dst)
|
|
{
|
|
assert(this.readable);
|
|
while (dst.length > 0) {
|
|
enforce(dst.length <= leastSize);
|
|
auto sz = min(dst.length, 4096);
|
|
enforce(.read(m_fileDescriptor, dst.ptr, cast(int)sz) == sz, "Failed to read data from disk.");
|
|
dst = dst[sz .. $];
|
|
m_ptr += sz;
|
|
yield();
|
|
}
|
|
}
|
|
|
|
void write(in ubyte[] bytes_)
|
|
{
|
|
const(ubyte)[] bytes = bytes_;
|
|
assert(this.writable);
|
|
while (bytes.length > 0) {
|
|
auto sz = min(bytes.length, 4096);
|
|
auto ret = .write(m_fileDescriptor, bytes.ptr, cast(int)sz);
|
|
import std.format : format;
|
|
enforce(ret == sz, format("Failed to write data to disk. %s %s %s %s", sz, errno, ret, m_fileDescriptor));
|
|
bytes = bytes[sz .. $];
|
|
m_ptr += sz;
|
|
yield();
|
|
}
|
|
}
|
|
|
|
void write(InputStream)(InputStream stream, ulong nbytes = 0)
|
|
{
|
|
writeDefault(stream, nbytes);
|
|
}
|
|
|
|
void flush()
|
|
{
|
|
assert(this.writable);
|
|
}
|
|
|
|
void finalize()
|
|
{
|
|
flush();
|
|
}
|
|
}
|
|
|
|
private void writeDefault(OutputStream, InputStream)(ref OutputStream dst, InputStream stream, ulong nbytes = 0)
|
|
{
|
|
assert(false);
|
|
/*
|
|
static struct Buffer { ubyte[64*1024] bytes = void; }
|
|
auto bufferobj = FreeListRef!(Buffer, false)();
|
|
auto buffer = bufferobj.bytes[];
|
|
|
|
//logTrace("default write %d bytes, empty=%s", nbytes, stream.empty);
|
|
if (nbytes == 0) {
|
|
while (!stream.empty) {
|
|
size_t chunk = min(stream.leastSize, buffer.length);
|
|
assert(chunk > 0, "leastSize returned zero for non-empty stream.");
|
|
//logTrace("read pipe chunk %d", chunk);
|
|
stream.read(buffer[0 .. chunk]);
|
|
dst.write(buffer[0 .. chunk]);
|
|
}
|
|
} else {
|
|
while (nbytes > 0) {
|
|
size_t chunk = min(nbytes, buffer.length);
|
|
//logTrace("read pipe chunk %d", chunk);
|
|
stream.read(buffer[0 .. chunk]);
|
|
dst.write(buffer[0 .. chunk]);
|
|
nbytes -= chunk;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
/**
|
|
Interface for directory watcher implementations.
|
|
|
|
Directory watchers monitor the contents of a directory (wither recursively or non-recursively)
|
|
for changes, such as file additions, deletions or modifications.
|
|
*/
|
|
interface DirectoryWatcher {
|
|
/// The path of the watched directory
|
|
@property Path path() const;
|
|
|
|
/// Indicates if the directory is watched recursively
|
|
@property bool recursive() const;
|
|
|
|
/** Fills the destination array with all changes that occurred since the last call.
|
|
|
|
The function will block until either directory changes have occurred or until the
|
|
timeout has elapsed. Specifying a negative duration will cause the function to
|
|
wait without a timeout.
|
|
|
|
Params:
|
|
dst = The destination array to which the changes will be appended
|
|
timeout = Optional timeout for the read operation
|
|
|
|
Returns:
|
|
If the call completed successfully, true is returned.
|
|
*/
|
|
bool readChanges(ref DirectoryChange[] dst, Duration timeout = dur!"seconds"(-1));
|
|
}
|
|
|
|
|
|
/** Specifies the kind of change in a watched directory.
|
|
*/
|
|
enum DirectoryChangeType {
|
|
/// A file or directory was added
|
|
added,
|
|
/// A file or directory was deleted
|
|
removed,
|
|
/// A file or directory was modified
|
|
modified
|
|
}
|
|
|
|
|
|
/** Describes a single change in a watched directory.
|
|
*/
|
|
struct DirectoryChange {
|
|
/// The type of change
|
|
DirectoryChangeType type;
|
|
|
|
/// Path of the file/directory that was changed
|
|
Path path;
|
|
}
|
|
|
|
|
|
private FileInfo makeFileInfo(DirEntry ent)
|
|
{
|
|
FileInfo ret;
|
|
ret.name = baseName(ent.name);
|
|
if( ret.name.length == 0 ) ret.name = ent.name;
|
|
assert(ret.name.length > 0);
|
|
ret.size = ent.size;
|
|
ret.timeModified = ent.timeLastModified;
|
|
version(Windows) ret.timeCreated = ent.timeCreated;
|
|
else ret.timeCreated = ent.timeLastModified;
|
|
ret.isSymlink = ent.isSymlink;
|
|
ret.isDirectory = ent.isDir;
|
|
return ret;
|
|
}
|