/**
File handling.
Copyright: © 2012 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 dub.internal.vibecompat.core.file;
public import dub.internal.vibecompat.inet.url;
import dub.internal.vibecompat.core.log;
import std.conv;
import std.c.stdio;
import std.datetime;
import std.exception;
import std.file;
import std.path;
static import std.stream;
import std.string;
import std.utf;
/* Add output range support to File
*/
struct RangeFile {
std.stream.File file;
void put(in ubyte[] bytes) { file.writeExact(bytes.ptr, bytes.length); }
void put(in char[] str) { put(cast(ubyte[])str); }
void put(char ch) { put((&ch)[0 .. 1]); }
void put(dchar ch) { char[4] chars; put(chars[0 .. encode(chars, ch)]); }
ubyte[] readAll()
{
file.seek(0, std.stream.SeekPos.End);
auto sz = file.position;
enforce(sz <= size_t.max, "File is too big to read to memory.");
file.seek(0, std.stream.SeekPos.Set);
auto ret = new ubyte[cast(size_t)sz];
file.readExact(ret.ptr, ret.length);
return ret;
}
void rawRead(ubyte[] dst) { file.readExact(dst.ptr, dst.length); }
void write(string str) { put(str); }
void close() { file.close(); }
void flush() { file.flush(); }
@property ulong size() { return file.size; }
}
/**
Opens a file stream with the specified mode.
*/
RangeFile openFile(Path path, FileMode mode = FileMode.Read)
{
std.stream.FileMode fmode;
final switch(mode){
case FileMode.Read: fmode = std.stream.FileMode.In; break;
case FileMode.ReadWrite: fmode = std.stream.FileMode.Out; break;
case FileMode.CreateTrunc: fmode = std.stream.FileMode.OutNew; break;
case FileMode.Append: fmode = std.stream.FileMode.Append; break;
}
auto ret = new std.stream.File(path.toNativeString(), fmode);
assert(ret.isOpen());
return RangeFile(ret);
}
/// ditto
RangeFile openFile(string path, FileMode mode = FileMode.Read)
{
return openFile(Path(path), mode);
}
/**
Moves or renames a file.
*/
void moveFile(Path from, Path to)
{
moveFile(from.toNativeString(), to.toNativeString());
}
/// ditto
void moveFile(string from, string to)
{
std.file.rename(from, to);
}
/**
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 excpetion 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)
{
if (existsFile(to)) {
enforce(overwrite, "Destination file already exists.");
// remove file before copy to allow "overwriting" files that are in
// use on Linux
removeFile(to);
}
.copy(from.toNativeString(), to.toNativeString());
// try to preserve ownership/permissions in Posix
version (Posix) {
import core.sys.posix.sys.stat;
import core.sys.posix.unistd;
import std.utf;
auto cspath = toUTFz!(const(char)*)(from.toNativeString());
auto cdpath = toUTFz!(const(char)*)(to.toNativeString());
stat_t st;
enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file.");
if (chown(cdpath, st.st_uid, st.st_gid) != 0)
st.st_mode &= ~(S_ISUID | S_ISGID);
chmod(cdpath, st.st_mode);
}
}
/// ditto
void copyFile(string from, string to)
{
copyFile(Path(from), Path(to));
}
/**
Creates a symlink.
*/
version (Windows)
alias symlinkFile = copyFile; // TODO: symlinks on Windows
else version (Posix)
{
void symlinkFile(Path from, Path to, bool overwrite = false)
{
if (existsFile(to)) {
enforce(overwrite, "Destination file already exists.");
// remove file before copy to allow "overwriting" files that are in
// use on Linux
removeFile(to);
}
.symlink(from.toNativeString(), to.toNativeString());
}
}
void symlinkFile(string from, string to)
{
symlinkFile(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) {
return existsFile(path.toNativeString());
}
/// ditto
bool existsFile(string path)
{
return std.file.exists(path);
}
/** Stores information about the specified file/directory into 'info'
Returns false if the file does not exist.
*/
FileInfo getFileInfo(Path path)
{
static if (__VERSION__ >= 2064)
auto ent = std.file.DirEntry(path.toNativeString());
else auto ent = std.file.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));
}
/**
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 and created otherwise and the 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.
*/
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.isSymlink = ent.isSymlink;
try {
ret.isDirectory = ent.isDir;
ret.size = ent.size;
ret.timeModified = ent.timeLastModified;
version(Windows) ret.timeCreated = ent.timeCreated;
else ret.timeCreated = ent.timeLastModified;
} catch (Exception e) {
logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg);
}
return ret;
}