/**
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 core.stdc.stdio;
import std.datetime;
import std.exception;
import std.file;
import std.path;
import std.stdio;
import std.string;
import std.utf;
/* Add output range support to File
*/
struct RangeFile {
@safe:
std.stdio.File file;
void put(scope const ubyte[] bytes) @trusted { file.rawWrite(bytes); }
void put(scope const char[] str) { put(cast(const(ubyte)[])str); }
void put(char ch) @trusted { put((&ch)[0 .. 1]); }
void put(dchar ch) { char[4] chars; put(chars[0 .. encode(chars, ch)]); }
ubyte[] readAll()
{
auto sz = this.size;
enforce(sz <= size_t.max, "File is too big to read to memory.");
() @trusted { file.seek(0, SEEK_SET); } ();
auto ret = new ubyte[cast(size_t)sz];
rawRead(ret);
return ret;
}
void rawRead(ubyte[] dst) @trusted { enforce(file.rawRead(dst).length == dst.length, "Failed to readall bytes from file."); }
void write(string str) { put(str); }
void close() @trusted { file.close(); }
void flush() @trusted { file.flush(); }
@property ulong size() @trusted { return file.size; }
}
/**
Opens a file stream with the specified mode.
*/
RangeFile openFile(NativePath path, FileMode mode = FileMode.read)
{
string fmode;
final switch(mode){
case FileMode.read: fmode = "rb"; break;
case FileMode.readWrite: fmode = "r+b"; break;
case FileMode.createTrunc: fmode = "wb"; break;
case FileMode.append: fmode = "ab"; break;
}
auto ret = std.stdio.File(path.toNativeString(), fmode);
assert(ret.isOpen);
return RangeFile(ret);
}
/// ditto
RangeFile openFile(string path, FileMode mode = FileMode.read)
{
return openFile(NativePath(path), mode);
}
/**
Moves or renames a file.
*/
void moveFile(NativePath from, NativePath 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 = NativePath of the source file
to = NativePath 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(NativePath from, NativePath to, bool overwrite = false)
{
enforce(existsFile(from), "Source file does not exist.");
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);
}
static if (is(PreserveAttributes))
{
.copy(from.toNativeString(), to.toNativeString(), PreserveAttributes.yes);
}
else
{
.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(NativePath(from), NativePath(to));
}
version (Windows) extern(Windows) int CreateHardLinkW(in wchar* to, in wchar* from, void* attr=null);
// guess whether 2 files are identical, ignores filename and content
private bool sameFile(NativePath a, NativePath b)
{
version (Posix) {
auto st_a = std.file.DirEntry(a.toNativeString).statBuf;
auto st_b = std.file.DirEntry(b.toNativeString).statBuf;
return st_a == st_b;
} else {
static assert(__traits(allMembers, FileInfo)[0] == "name");
return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $];
}
}
/**
Creates a hardlink.
*/
void hardLinkFile(NativePath from, NativePath to, bool overwrite = false)
{
if (existsFile(to)) {
enforce(overwrite, "Destination file already exists.");
if (auto fe = collectException!FileException(removeFile(to))) {
if (sameFile(from, to)) return;
throw fe;
}
}
version (Windows)
{
alias cstr = toUTFz!(const(wchar)*);
if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString)))
return;
}
else
{
import core.sys.posix.unistd : link;
alias cstr = toUTFz!(const(char)*);
if (!link(cstr(from.toNativeString), cstr(to.toNativeString)))
return;
}
// fallback to copy
copyFile(from, to, overwrite);
}
/**
Removes a file
*/
void removeFile(NativePath path)
{
removeFile(path.toNativeString());
}
/// ditto
void removeFile(string path) {
std.file.remove(path);
}
/**
Checks if a file exists
*/
bool existsFile(NativePath 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(NativePath path)
{
auto ent = std.file.DirEntry(path.toNativeString());
return makeFileInfo(ent);
}
/// ditto
FileInfo getFileInfo(string path)
{
return getFileInfo(NativePath(path));
}
/**
Creates a new directory.
*/
void createDirectory(NativePath path)
{
mkdir(path.toNativeString());
}
/// ditto
void createDirectory(string path)
{
createDirectory(NativePath(path));
}
/**
Enumerates all files in the specified directory.
*/
void listDirectory(NativePath 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(NativePath(path), del);
}
/// ditto
int delegate(scope int delegate(ref FileInfo)) iterateDirectory(NativePath 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(NativePath(path));
}
/**
Returns the current working directory.
*/
NativePath getWorkingDirectory()
{
return NativePath(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;
}