Initial commit.
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.
This commit is contained in:
commit
7e2d1dd038
22 changed files with 9977 additions and 0 deletions
225
source/vibe/core/args.d
Normal file
225
source/vibe/core/args.d
Normal file
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
Parses and allows querying the command line arguments and configuration
|
||||
file.
|
||||
|
||||
The optional configuration file (vibe.conf) is a JSON file, containing an
|
||||
object with the keys corresponding to option names, and values corresponding
|
||||
to their values. It is searched for in the local directory, user's home
|
||||
directory, or /etc/vibe/ (POSIX only), whichever is found first.
|
||||
|
||||
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, Vladimir Panteleev
|
||||
*/
|
||||
module vibe.core.args;
|
||||
|
||||
import vibe.core.log;
|
||||
//import vibe.data.json;
|
||||
|
||||
import std.algorithm : any, map, sort;
|
||||
import std.array : array, join, replicate, split;
|
||||
import std.exception;
|
||||
import std.file;
|
||||
import std.getopt;
|
||||
import std.path : buildPath;
|
||||
import std.string : format, stripRight, wrap;
|
||||
|
||||
import core.runtime;
|
||||
|
||||
|
||||
/**
|
||||
Finds and reads an option from the configuration file or command line.
|
||||
|
||||
Command line options take precedence over configuration file entries.
|
||||
|
||||
Params:
|
||||
names = Option names. Separate multiple name variants with "|",
|
||||
as for $(D std.getopt).
|
||||
pvalue = Pointer to store the value. Unchanged if value was not found.
|
||||
help_text = Text to be displayed when the application is run with
|
||||
--help.
|
||||
|
||||
Returns:
|
||||
$(D true) if the value was found, $(D false) otherwise.
|
||||
|
||||
See_Also: readRequiredOption
|
||||
*/
|
||||
bool readOption(T)(string names, T* pvalue, string help_text)
|
||||
{
|
||||
// May happen due to http://d.puremagic.com/issues/show_bug.cgi?id=9881
|
||||
if (g_args is null) init();
|
||||
|
||||
OptionInfo info;
|
||||
info.names = names.split("|").sort!((a, b) => a.length < b.length)().array();
|
||||
info.hasValue = !is(T == bool);
|
||||
info.helpText = help_text;
|
||||
assert(!g_options.any!(o => o.names == info.names)(), "readOption() may only be called once per option name.");
|
||||
g_options ~= info;
|
||||
|
||||
immutable olen = g_args.length;
|
||||
getopt(g_args, getoptConfig, names, pvalue);
|
||||
if (g_args.length < olen) return true;
|
||||
|
||||
/*if (g_haveConfig) {
|
||||
foreach (name; info.names)
|
||||
if (auto pv = name in g_config) {
|
||||
*pvalue = pv.to!T;
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
The same as readOption, but throws an exception if the given option is missing.
|
||||
|
||||
See_Also: readOption
|
||||
*/
|
||||
T readRequiredOption(T)(string names, string help_text)
|
||||
{
|
||||
string formattedNames() {
|
||||
return names.split("|").map!(s => s.length == 1 ? "-" ~ s : "--" ~ s).join("/");
|
||||
}
|
||||
T ret;
|
||||
enforce(readOption(names, &ret, help_text) || g_help,
|
||||
format("Missing mandatory option %s.", formattedNames()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Prints a help screen consisting of all options encountered in getOption calls.
|
||||
*/
|
||||
void printCommandLineHelp()
|
||||
{
|
||||
enum dcolumn = 20;
|
||||
enum ncolumns = 80;
|
||||
|
||||
logInfo("Usage: %s <options>\n", g_args[0]);
|
||||
foreach (opt; g_options) {
|
||||
string shortopt;
|
||||
string[] longopts;
|
||||
if (opt.names[0].length == 1 && !opt.hasValue) {
|
||||
shortopt = "-"~opt.names[0];
|
||||
longopts = opt.names[1 .. $];
|
||||
} else {
|
||||
shortopt = " ";
|
||||
longopts = opt.names;
|
||||
}
|
||||
|
||||
string optionString(string name)
|
||||
{
|
||||
if (name.length == 1) return "-"~name~(opt.hasValue ? " <value>" : "");
|
||||
else return "--"~name~(opt.hasValue ? "=<value>" : "");
|
||||
}
|
||||
|
||||
string[] lopts; foreach(lo; longopts) lopts ~= optionString(lo);
|
||||
auto optstr = format(" %s %s", shortopt, lopts.join(", "));
|
||||
if (optstr.length < dcolumn) optstr ~= replicate(" ", dcolumn - optstr.length);
|
||||
|
||||
auto indent = replicate(" ", dcolumn+1);
|
||||
auto desc = wrap(opt.helpText, ncolumns - dcolumn - 2, optstr.length > dcolumn ? indent : "", indent).stripRight();
|
||||
|
||||
if (optstr.length > dcolumn)
|
||||
logInfo("%s\n%s", optstr, desc);
|
||||
else logInfo("%s %s", optstr, desc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Checks for unrecognized command line options and display a help screen.
|
||||
|
||||
This function is called automatically from vibe.appmain to check for
|
||||
correct command line usage. It will print a help screen in case of
|
||||
unrecognized options.
|
||||
|
||||
Params:
|
||||
args_out = Optional parameter for storing any arguments not handled
|
||||
by any readOption call. If this is left to null, an error
|
||||
will be triggered whenever unhandled arguments exist.
|
||||
|
||||
Returns:
|
||||
If "--help" was passed, the function returns false. In all other
|
||||
cases either true is returned or an exception is thrown.
|
||||
*/
|
||||
bool finalizeCommandLineOptions(string[]* args_out = null)
|
||||
{
|
||||
scope(exit) g_args = null;
|
||||
|
||||
if (args_out) {
|
||||
*args_out = g_args;
|
||||
} else if (g_args.length > 1) {
|
||||
logError("Unrecognized command line option: %s\n", g_args[1]);
|
||||
printCommandLineHelp();
|
||||
throw new Exception("Unrecognized command line option.");
|
||||
}
|
||||
|
||||
if (g_help) {
|
||||
printCommandLineHelp();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private struct OptionInfo {
|
||||
string[] names;
|
||||
bool hasValue;
|
||||
string helpText;
|
||||
}
|
||||
|
||||
private {
|
||||
__gshared string[] g_args;
|
||||
__gshared bool g_haveConfig;
|
||||
//__gshared Json g_config;
|
||||
__gshared OptionInfo[] g_options;
|
||||
__gshared bool g_help;
|
||||
}
|
||||
|
||||
private string[] getConfigPaths()
|
||||
{
|
||||
string[] result = [""];
|
||||
import std.process : environment;
|
||||
version (Windows)
|
||||
result ~= environment.get("USERPROFILE");
|
||||
else
|
||||
result ~= [environment.get("HOME"), "/etc/vibe/"];
|
||||
return result;
|
||||
}
|
||||
|
||||
// this is invoked by the first readOption call (at least vibe.core will perform one)
|
||||
private void init()
|
||||
{
|
||||
version (VibeDisableCommandLineParsing) {}
|
||||
else g_args = Runtime.args;
|
||||
|
||||
if (!g_args.length) g_args = ["dummy"];
|
||||
|
||||
// TODO: let different config files override individual fields
|
||||
auto searchpaths = getConfigPaths();
|
||||
foreach (spath; searchpaths) {
|
||||
auto cpath = buildPath(spath, configName);
|
||||
if (cpath.exists) {
|
||||
scope(failure) logError("Failed to parse config file %s.", cpath);
|
||||
auto text = cpath.readText();
|
||||
//g_config = text.parseJson();
|
||||
g_haveConfig = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_haveConfig)
|
||||
logDiagnostic("No config file found in %s", searchpaths);
|
||||
|
||||
readOption("h|help", &g_help, "Prints this help screen.");
|
||||
}
|
||||
|
||||
private enum configName = "vibe.conf";
|
||||
|
||||
private template ValueTuple(T...) { alias ValueTuple = T; }
|
||||
|
||||
private alias getoptConfig = ValueTuple!(std.getopt.config.passThrough, std.getopt.config.bundling);
|
1190
source/vibe/core/concurrency.d
Normal file
1190
source/vibe/core/concurrency.d
Normal file
File diff suppressed because it is too large
Load diff
149
source/vibe/core/connectionpool.d
Normal file
149
source/vibe/core/connectionpool.d
Normal file
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
Generic connection pool for reusing persistent connections across fibers.
|
||||
|
||||
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.connectionpool;
|
||||
|
||||
import vibe.core.log;
|
||||
|
||||
import core.thread;
|
||||
import vibe.core.sync;
|
||||
//import vibe.utils.memory;
|
||||
|
||||
/**
|
||||
Generic connection pool class.
|
||||
|
||||
The connection pool is creating connections using the supplied factory
|
||||
function as needed whenever `lockConnection` is called. Connections are
|
||||
associated to the calling fiber, as long as any copy of the returned
|
||||
`LockedConnection` object still exists. Connections that are not associated
|
||||
to any fiber will be kept in a pool of open connections for later reuse.
|
||||
|
||||
Note that, after retrieving a connection with `lockConnection`, the caller
|
||||
has to make sure that the connection is actually physically open and to
|
||||
reopen it if necessary. The `ConnectionPool` class has no knowledge of the
|
||||
internals of the connection objects.
|
||||
*/
|
||||
class ConnectionPool(Connection)
|
||||
{
|
||||
private {
|
||||
Connection delegate() m_connectionFactory;
|
||||
Connection[] m_connections;
|
||||
int[const(Connection)] m_lockCount;
|
||||
FreeListRef!LocalTaskSemaphore m_sem;
|
||||
debug Thread m_thread;
|
||||
}
|
||||
|
||||
this(Connection delegate() connection_factory, uint max_concurrent = uint.max)
|
||||
{
|
||||
m_connectionFactory = connection_factory;
|
||||
m_sem = FreeListRef!LocalTaskSemaphore(max_concurrent);
|
||||
debug m_thread = Thread.getThis();
|
||||
}
|
||||
|
||||
/** Determines the maximum number of concurrently open connections.
|
||||
|
||||
Attempting to lock more connections that this number will cause the
|
||||
calling fiber to be blocked until one of the locked connections
|
||||
becomes available for reuse.
|
||||
*/
|
||||
@property void maxConcurrency(uint max_concurrent) {
|
||||
m_sem.maxLocks = max_concurrent;
|
||||
}
|
||||
/// ditto
|
||||
@property uint maxConcurrency() {
|
||||
return m_sem.maxLocks;
|
||||
}
|
||||
|
||||
/** Retrieves a connection to temporarily associate with the calling fiber.
|
||||
|
||||
The returned `LockedConnection` object uses RAII and reference counting
|
||||
to determine when to unlock the connection.
|
||||
*/
|
||||
LockedConnection!Connection lockConnection()
|
||||
{
|
||||
debug assert(m_thread is Thread.getThis(), "ConnectionPool was called from a foreign thread!");
|
||||
|
||||
m_sem.lock();
|
||||
size_t cidx = size_t.max;
|
||||
foreach( i, c; m_connections ){
|
||||
auto plc = c in m_lockCount;
|
||||
if( !plc || *plc == 0 ){
|
||||
cidx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Connection conn;
|
||||
if( cidx != size_t.max ){
|
||||
logTrace("returning %s connection %d of %d", Connection.stringof, cidx, m_connections.length);
|
||||
conn = m_connections[cidx];
|
||||
} else {
|
||||
logDebug("creating new %s connection, all %d are in use", Connection.stringof, m_connections.length);
|
||||
conn = m_connectionFactory(); // NOTE: may block
|
||||
logDebug(" ... %s", cast(void*)conn);
|
||||
}
|
||||
m_lockCount[conn] = 1;
|
||||
if( cidx == size_t.max ){
|
||||
m_connections ~= conn;
|
||||
logDebug("Now got %d connections", m_connections.length);
|
||||
}
|
||||
auto ret = LockedConnection!Connection(this, conn);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
struct LockedConnection(Connection) {
|
||||
private {
|
||||
ConnectionPool!Connection m_pool;
|
||||
Task m_task;
|
||||
Connection m_conn;
|
||||
debug uint m_magic = 0xB1345AC2;
|
||||
}
|
||||
|
||||
private this(ConnectionPool!Connection pool, Connection conn)
|
||||
{
|
||||
assert(conn !is null);
|
||||
m_pool = pool;
|
||||
m_conn = conn;
|
||||
m_task = Task.getThis();
|
||||
}
|
||||
|
||||
this(this)
|
||||
{
|
||||
debug assert(m_magic == 0xB1345AC2, "LockedConnection value corrupted.");
|
||||
if( m_conn ){
|
||||
auto fthis = Task.getThis();
|
||||
assert(fthis is m_task);
|
||||
m_pool.m_lockCount[m_conn]++;
|
||||
logTrace("conn %s copy %d", cast(void*)m_conn, m_pool.m_lockCount[m_conn]);
|
||||
}
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
debug assert(m_magic == 0xB1345AC2, "LockedConnection value corrupted.");
|
||||
if( m_conn ){
|
||||
auto fthis = Task.getThis();
|
||||
assert(fthis is m_task, "Locked connection destroyed in foreign task.");
|
||||
auto plc = m_conn in m_pool.m_lockCount;
|
||||
assert(plc !is null);
|
||||
assert(*plc >= 1);
|
||||
//logTrace("conn %s destroy %d", cast(void*)m_conn, *plc-1);
|
||||
if( --*plc == 0 ){
|
||||
m_pool.m_sem.unlock();
|
||||
//logTrace("conn %s release", cast(void*)m_conn);
|
||||
}
|
||||
m_conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@property int __refCount() const { return m_pool.m_lockCount.get(m_conn, 0); }
|
||||
@property inout(Connection) __conn() inout { return m_conn; }
|
||||
|
||||
alias __conn this;
|
||||
}
|
1844
source/vibe/core/core.d
Normal file
1844
source/vibe/core/core.d
Normal file
File diff suppressed because it is too large
Load diff
638
source/vibe/core/file.d
Normal file
638
source/vibe/core/file.d
Normal file
|
@ -0,0 +1,638 @@
|
|||
/**
|
||||
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;
|
||||
}
|
879
source/vibe/core/log.d
Normal file
879
source/vibe/core/log.d
Normal file
|
@ -0,0 +1,879 @@
|
|||
/**
|
||||
Central logging facility for vibe.
|
||||
|
||||
Copyright: © 2012-2014 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.log;
|
||||
|
||||
import vibe.core.args;
|
||||
import vibe.core.concurrency : ScopedLock, lock;
|
||||
import vibe.core.sync;
|
||||
|
||||
import std.algorithm;
|
||||
import std.array;
|
||||
import std.datetime;
|
||||
import std.format;
|
||||
import std.stdio;
|
||||
import core.atomic;
|
||||
import core.thread;
|
||||
|
||||
import std.traits : isSomeString;
|
||||
import std.range.primitives : isInputRange, isOutputRange;
|
||||
|
||||
/**
|
||||
Sets the minimum log level to be printed using the default console logger.
|
||||
|
||||
This level applies to the default stdout/stderr logger only.
|
||||
*/
|
||||
void setLogLevel(LogLevel level)
|
||||
nothrow @safe {
|
||||
if (ss_stdoutLogger)
|
||||
ss_stdoutLogger.lock().minLevel = level;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Sets the log format used for the default console logger.
|
||||
|
||||
This level applies to the default stdout/stderr logger only.
|
||||
|
||||
Params:
|
||||
fmt = The log format for the stderr (default is `FileLogger.Format.thread`)
|
||||
infoFmt = The log format for the stdout (default is `FileLogger.Format.plain`)
|
||||
*/
|
||||
void setLogFormat(FileLogger.Format fmt, FileLogger.Format infoFmt = FileLogger.Format.plain)
|
||||
nothrow @safe {
|
||||
if (ss_stdoutLogger) {
|
||||
auto l = ss_stdoutLogger.lock();
|
||||
l.format = fmt;
|
||||
l.infoFormat = infoFmt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Sets a log file for disk file logging.
|
||||
|
||||
Multiple calls to this function will register multiple log
|
||||
files for output.
|
||||
*/
|
||||
void setLogFile(string filename, LogLevel min_level = LogLevel.error)
|
||||
{
|
||||
auto logger = cast(shared)new FileLogger(filename);
|
||||
{
|
||||
auto l = logger.lock();
|
||||
l.minLevel = min_level;
|
||||
l.format = FileLogger.Format.threadTime;
|
||||
}
|
||||
registerLogger(logger);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Registers a new logger instance.
|
||||
|
||||
The specified Logger will receive all log messages in its Logger.log
|
||||
method after it has been registered.
|
||||
|
||||
Examples:
|
||||
---
|
||||
auto logger = cast(shared)new HTMLLogger("log.html");
|
||||
logger.lock().format = FileLogger.Format.threadTime;
|
||||
registerLogger(logger);
|
||||
---
|
||||
|
||||
See_Also: deregisterLogger
|
||||
*/
|
||||
void registerLogger(shared(Logger) logger)
|
||||
nothrow {
|
||||
ss_loggers ~= logger;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Deregisters an active logger instance.
|
||||
|
||||
See_Also: registerLogger
|
||||
*/
|
||||
void deregisterLogger(shared(Logger) logger)
|
||||
nothrow {
|
||||
for (size_t i = 0; i < ss_loggers.length; ) {
|
||||
if (ss_loggers[i] !is logger) i++;
|
||||
else ss_loggers = ss_loggers[0 .. i] ~ ss_loggers[i+1 .. $];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Logs a message.
|
||||
|
||||
Params:
|
||||
level = The log level for the logged message
|
||||
fmt = See http://dlang.org/phobos/std_format.html#format-string
|
||||
args = Any input values needed for formatting
|
||||
*/
|
||||
void log(LogLevel level, /*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args)
|
||||
nothrow if (isSomeString!S)
|
||||
{
|
||||
static assert(level != LogLevel.none);
|
||||
try {
|
||||
foreach (l; getLoggers())
|
||||
if (l.minLevel <= level) { // WARNING: TYPE SYSTEM HOLE: accessing field of shared class!
|
||||
auto ll = l.lock();
|
||||
auto rng = LogOutputRange(ll, file, line, level);
|
||||
/*() @trusted {*/ rng.formattedWrite(fmt, args); //} (); // formattedWrite is not @safe at least up to 2.068.0
|
||||
rng.finalize();
|
||||
}
|
||||
} catch(Exception e) debug assert(false, e.msg);
|
||||
}
|
||||
/// ditto
|
||||
void logTrace(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.trace/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logDebugV(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.debugV/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logDebug(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.debug_/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logDiagnostic(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.diagnostic/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logInfo(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.info/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logWarn(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.warn/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logError(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.error/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logCritical(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.critical/*, mod, func*/, file, line)(fmt, args); }
|
||||
/// ditto
|
||||
void logFatal(string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.fatal, file, line)(fmt, args); }
|
||||
|
||||
///
|
||||
@safe unittest {
|
||||
void test() nothrow
|
||||
{
|
||||
logInfo("Hello, World!");
|
||||
logWarn("This may not be %s.", "good");
|
||||
log!(LogLevel.info)("This is a %s.", "test");
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the log level for a particular log message.
|
||||
enum LogLevel {
|
||||
trace, /// Developer information for locating events when no useful stack traces are available
|
||||
debugV, /// Developer information useful for algorithm debugging - for verbose output
|
||||
debug_, /// Developer information useful for algorithm debugging
|
||||
diagnostic, /// Extended user information (e.g. for more detailed error information)
|
||||
info, /// Informational message for normal user education
|
||||
warn, /// Unexpected condition that could indicate an error but has no direct consequences
|
||||
error, /// Normal error that is handled gracefully
|
||||
critical, /// Error that severely influences the execution of the application
|
||||
fatal, /// Error that forces the application to terminate
|
||||
none, /// Special value used to indicate no logging when set as the minimum log level
|
||||
|
||||
verbose1 = diagnostic, /// Alias for diagnostic messages
|
||||
verbose2 = debug_, /// Alias for debug messages
|
||||
verbose3 = debugV, /// Alias for verbose debug messages
|
||||
verbose4 = trace, /// Alias for trace messages
|
||||
}
|
||||
|
||||
/// Represents a single logged line
|
||||
struct LogLine {
|
||||
string mod;
|
||||
string func;
|
||||
string file;
|
||||
int line;
|
||||
LogLevel level;
|
||||
Thread thread;
|
||||
string threadName;
|
||||
uint threadID;
|
||||
Fiber fiber;
|
||||
uint fiberID;
|
||||
SysTime time;
|
||||
string text; /// Legacy field used in `Logger.log`
|
||||
}
|
||||
|
||||
/// Abstract base class for all loggers
|
||||
class Logger {
|
||||
LogLevel minLevel = LogLevel.min;
|
||||
|
||||
private {
|
||||
LogLine m_curLine;
|
||||
Appender!string m_curLineText;
|
||||
}
|
||||
|
||||
final bool acceptsLevel(LogLevel value) nothrow pure @safe { return value >= this.minLevel; }
|
||||
|
||||
/** Legacy logging interface relying on dynamic memory allocation.
|
||||
|
||||
Override `beginLine`, `put`, `endLine` instead for a more efficient and
|
||||
possibly allocation-free implementation.
|
||||
*/
|
||||
void log(ref LogLine line) @safe {}
|
||||
|
||||
/// Starts a new log line.
|
||||
void beginLine(ref LogLine line_info)
|
||||
@safe {
|
||||
m_curLine = line_info;
|
||||
m_curLineText = appender!string();
|
||||
}
|
||||
|
||||
/// Writes part of a log line message.
|
||||
void put(scope const(char)[] text)
|
||||
@safe {
|
||||
m_curLineText.put(text);
|
||||
}
|
||||
|
||||
/// Finalizes a log line.
|
||||
void endLine()
|
||||
@safe {
|
||||
m_curLine.text = m_curLineText.data;
|
||||
log(m_curLine);
|
||||
m_curLine.text = null;
|
||||
m_curLineText = Appender!string.init;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Plain-text based logger for logging to regular files or stdout/stderr
|
||||
*/
|
||||
final class FileLogger : Logger {
|
||||
/// The log format used by the FileLogger
|
||||
enum Format {
|
||||
plain, /// Output only the plain log message
|
||||
thread, /// Prefix "[thread-id:fiber-id loglevel]"
|
||||
threadTime /// Prefix "[thread-id:fiber-id timestamp loglevel]"
|
||||
}
|
||||
|
||||
private {
|
||||
File m_infoFile;
|
||||
File m_diagFile;
|
||||
File m_curFile;
|
||||
}
|
||||
|
||||
Format format = Format.thread;
|
||||
Format infoFormat = Format.plain;
|
||||
|
||||
this(File info_file, File diag_file)
|
||||
{
|
||||
m_infoFile = info_file;
|
||||
m_diagFile = diag_file;
|
||||
}
|
||||
|
||||
this(string filename)
|
||||
{
|
||||
m_infoFile = File(filename, "ab");
|
||||
m_diagFile = m_infoFile;
|
||||
}
|
||||
|
||||
override void beginLine(ref LogLine msg)
|
||||
@trusted // FILE isn't @safe (as of DMD 2.065)
|
||||
{
|
||||
string pref;
|
||||
final switch (msg.level) {
|
||||
case LogLevel.trace: pref = "trc"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.debugV: pref = "dbv"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.debug_: pref = "dbg"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.diagnostic: pref = "dia"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.info: pref = "INF"; m_curFile = m_infoFile; break;
|
||||
case LogLevel.warn: pref = "WRN"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.error: pref = "ERR"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.critical: pref = "CRITICAL"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.fatal: pref = "FATAL"; m_curFile = m_diagFile; break;
|
||||
case LogLevel.none: assert(false);
|
||||
}
|
||||
|
||||
auto fmt = (m_curFile is m_diagFile) ? this.format : this.infoFormat;
|
||||
|
||||
final switch (fmt) {
|
||||
case Format.plain: break;
|
||||
case Format.thread: m_curFile.writef("[%08X:%08X %s] ", msg.threadID, msg.fiberID, pref); break;
|
||||
case Format.threadTime:
|
||||
auto tm = msg.time;
|
||||
static if (is(typeof(tm.fracSecs))) auto msecs = tm.fracSecs.total!"msecs"; // 2.069 has deprecated "fracSec"
|
||||
else auto msecs = tm.fracSec.msecs;
|
||||
m_curFile.writef("[%08X:%08X %d.%02d.%02d %02d:%02d:%02d.%03d %s] ",
|
||||
msg.threadID, msg.fiberID,
|
||||
tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second, msecs,
|
||||
pref);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
override void put(scope const(char)[] text)
|
||||
{
|
||||
static if (__VERSION__ <= 2066)
|
||||
() @trusted { m_curFile.write(text); } ();
|
||||
else m_curFile.write(text);
|
||||
}
|
||||
|
||||
override void endLine()
|
||||
{
|
||||
static if (__VERSION__ <= 2066)
|
||||
() @trusted { m_curFile.writeln(); } ();
|
||||
else m_curFile.writeln();
|
||||
m_curFile.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Logger implementation for logging to an HTML file with dynamic filtering support.
|
||||
*/
|
||||
final class HTMLLogger : Logger {
|
||||
private {
|
||||
File m_logFile;
|
||||
}
|
||||
|
||||
this(string filename = "log.html")
|
||||
{
|
||||
m_logFile = File(filename, "wt");
|
||||
writeHeader();
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
//version(FinalizerDebug) writeln("HtmlLogWritet.~this");
|
||||
writeFooter();
|
||||
m_logFile.close();
|
||||
//version(FinalizerDebug) writeln("HtmlLogWritet.~this out");
|
||||
}
|
||||
|
||||
@property void minLogLevel(LogLevel value) pure nothrow @safe { this.minLevel = value; }
|
||||
|
||||
override void beginLine(ref LogLine msg)
|
||||
@trusted // FILE isn't @safe (as of DMD 2.065)
|
||||
{
|
||||
if( !m_logFile.isOpen ) return;
|
||||
|
||||
final switch (msg.level) {
|
||||
case LogLevel.none: assert(false);
|
||||
case LogLevel.trace: m_logFile.write(`<div class="trace">`); break;
|
||||
case LogLevel.debugV: m_logFile.write(`<div class="debugv">`); break;
|
||||
case LogLevel.debug_: m_logFile.write(`<div class="debug">`); break;
|
||||
case LogLevel.diagnostic: m_logFile.write(`<div class="diagnostic">`); break;
|
||||
case LogLevel.info: m_logFile.write(`<div class="info">`); break;
|
||||
case LogLevel.warn: m_logFile.write(`<div class="warn">`); break;
|
||||
case LogLevel.error: m_logFile.write(`<div class="error">`); break;
|
||||
case LogLevel.critical: m_logFile.write(`<div class="critical">`); break;
|
||||
case LogLevel.fatal: m_logFile.write(`<div class="fatal">`); break;
|
||||
}
|
||||
m_logFile.writef(`<div class="timeStamp">%s</div>`, msg.time.toISOExtString());
|
||||
if (msg.thread)
|
||||
m_logFile.writef(`<div class="threadName">%s</div>`, msg.thread.name);
|
||||
m_logFile.write(`<div class="message">`);
|
||||
}
|
||||
|
||||
override void put(scope const(char)[] text)
|
||||
{
|
||||
auto dst = () @trusted { return m_logFile.lockingTextWriter(); } (); // LockingTextWriter not @safe for DMD 2.066
|
||||
while (!text.empty && (text.front == ' ' || text.front == '\t')) {
|
||||
foreach (i; 0 .. text.front == ' ' ? 1 : 4)
|
||||
() @trusted { dst.put(" "); } (); // LockingTextWriter not @safe for DMD 2.066
|
||||
text.popFront();
|
||||
}
|
||||
() @trusted { filterHTMLEscape(dst, text); } (); // LockingTextWriter not @safe for DMD 2.066
|
||||
}
|
||||
|
||||
override void endLine()
|
||||
{
|
||||
() @trusted { // not @safe for DMD 2.066
|
||||
m_logFile.write(`</div>`);
|
||||
m_logFile.writeln(`</div>`);
|
||||
} ();
|
||||
m_logFile.flush();
|
||||
}
|
||||
|
||||
private void writeHeader(){
|
||||
if( !m_logFile.isOpen ) return;
|
||||
|
||||
m_logFile.writeln(
|
||||
`<html>
|
||||
<head>
|
||||
<title>HTML Log</title>
|
||||
<style content="text/css">
|
||||
.trace { position: relative; color: #E0E0E0; font-size: 9pt; }
|
||||
.debugv { position: relative; color: #E0E0E0; font-size: 9pt; }
|
||||
.debug { position: relative; color: #808080; }
|
||||
.diagnostic { position: relative; color: #808080; }
|
||||
.info { position: relative; color: black; }
|
||||
.warn { position: relative; color: #E08000; }
|
||||
.error { position: relative; color: red; }
|
||||
.critical { position: relative; color: red; background-color: black; }
|
||||
.fatal { position: relative; color: red; background-color: black; }
|
||||
|
||||
.log { margin-left: 10pt; }
|
||||
.code {
|
||||
font-family: "Courier New";
|
||||
background-color: #F0F0F0;
|
||||
border: 1px solid gray;
|
||||
margin-bottom: 10pt;
|
||||
margin-left: 30pt;
|
||||
margin-right: 10pt;
|
||||
padding-left: 0pt;
|
||||
}
|
||||
|
||||
div.timeStamp {
|
||||
position: absolute;
|
||||
width: 150pt;
|
||||
}
|
||||
div.threadName {
|
||||
position: absolute;
|
||||
top: 0pt;
|
||||
left: 150pt;
|
||||
width: 100pt;
|
||||
}
|
||||
div.message {
|
||||
position: relative;
|
||||
top: 0pt;
|
||||
left: 250pt;
|
||||
}
|
||||
body {
|
||||
font-family: Tahoma, Arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
</style>
|
||||
<script language="JavaScript">
|
||||
function enableStyle(i){
|
||||
var style = document.styleSheets[0].cssRules[i].style;
|
||||
style.display = "block";
|
||||
}
|
||||
|
||||
function disableStyle(i){
|
||||
var style = document.styleSheets[0].cssRules[i].style;
|
||||
style.display = "none";
|
||||
}
|
||||
|
||||
function updateLevels(){
|
||||
var sel = document.getElementById("Level");
|
||||
var level = sel.value;
|
||||
for( i = 0; i < level; i++ ) disableStyle(i);
|
||||
for( i = level; i < 5; i++ ) enableStyle(i);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body style="padding: 0px; margin: 0px;" onLoad="updateLevels(); updateCode();">
|
||||
<div style="position: fixed; z-index: 100; padding: 4pt; width:100%; background-color: lightgray; border-bottom: 1px solid black;">
|
||||
<form style="margin: 0px;">
|
||||
Minimum Log Level:
|
||||
<select id="Level" onChange="updateLevels()">
|
||||
<option value="0">Trace</option>
|
||||
<option value="1">Verbose</option>
|
||||
<option value="2">Debug</option>
|
||||
<option value="3">Diagnostic</option>
|
||||
<option value="4">Info</option>
|
||||
<option value="5">Warn</option>
|
||||
<option value="6">Error</option>
|
||||
<option value="7">Critical</option>
|
||||
<option value="8">Fatal</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
<div style="height: 30pt;"></div>
|
||||
<div class="log">`);
|
||||
m_logFile.flush();
|
||||
}
|
||||
|
||||
private void writeFooter(){
|
||||
if( !m_logFile.isOpen ) return;
|
||||
|
||||
m_logFile.writeln(
|
||||
` </div>
|
||||
</body>
|
||||
</html>`);
|
||||
m_logFile.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Helper stuff.
|
||||
*/
|
||||
/** Writes the HTML escaped version of a given string to an output range.
|
||||
*/
|
||||
void filterHTMLEscape(R, S)(ref R dst, S str, HTMLEscapeFlags flags = HTMLEscapeFlags.escapeNewline)
|
||||
if (isOutputRange!(R, dchar) && isInputRange!S)
|
||||
{
|
||||
for (;!str.empty;str.popFront())
|
||||
filterHTMLEscape(dst, str.front, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
Writes the HTML escaped version of a character to an output range.
|
||||
*/
|
||||
void filterHTMLEscape(R)(ref R dst, dchar ch, HTMLEscapeFlags flags = HTMLEscapeFlags.escapeNewline )
|
||||
{
|
||||
switch (ch) {
|
||||
default:
|
||||
if (flags & HTMLEscapeFlags.escapeUnknown) {
|
||||
dst.put("&#");
|
||||
dst.put(to!string(cast(uint)ch));
|
||||
dst.put(';');
|
||||
} else dst.put(ch);
|
||||
break;
|
||||
case '"':
|
||||
if (flags & HTMLEscapeFlags.escapeQuotes) dst.put(""");
|
||||
else dst.put('"');
|
||||
break;
|
||||
case '\'':
|
||||
if (flags & HTMLEscapeFlags.escapeQuotes) dst.put("'");
|
||||
else dst.put('\'');
|
||||
break;
|
||||
case '\r', '\n':
|
||||
if (flags & HTMLEscapeFlags.escapeNewline) {
|
||||
dst.put("&#");
|
||||
dst.put(to!string(cast(uint)ch));
|
||||
dst.put(';');
|
||||
} else dst.put(ch);
|
||||
break;
|
||||
case 'a': .. case 'z': goto case;
|
||||
case 'A': .. case 'Z': goto case;
|
||||
case '0': .. case '9': goto case;
|
||||
case ' ', '\t', '-', '_', '.', ':', ',', ';',
|
||||
'#', '+', '*', '?', '=', '(', ')', '/', '!',
|
||||
'%' , '{', '}', '[', ']', '`', '´', '$', '^', '~':
|
||||
dst.put(cast(char)ch);
|
||||
break;
|
||||
case '<': dst.put("<"); break;
|
||||
case '>': dst.put(">"); break;
|
||||
case '&': dst.put("&"); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum HTMLEscapeFlags {
|
||||
escapeMinimal = 0,
|
||||
escapeQuotes = 1<<0,
|
||||
escapeNewline = 1<<1,
|
||||
escapeUnknown = 1<<2
|
||||
}
|
||||
/*****************************
|
||||
*/
|
||||
|
||||
import std.conv;
|
||||
/**
|
||||
A logger that logs in syslog format according to RFC 5424.
|
||||
|
||||
Messages can be logged to files (via file streams) or over the network (via
|
||||
TCP or SSL streams).
|
||||
|
||||
Standards: Conforms to RFC 5424.
|
||||
*/
|
||||
final class SyslogLogger(OutputStream) : Logger {
|
||||
private {
|
||||
string m_hostName;
|
||||
string m_appName;
|
||||
OutputStream m_ostream;
|
||||
Facility m_facility;
|
||||
}
|
||||
|
||||
/// Facilities
|
||||
enum Facility {
|
||||
kern, /// kernel messages
|
||||
user, /// user-level messages
|
||||
mail, /// mail system
|
||||
daemon, /// system daemons
|
||||
auth, /// security/authorization messages
|
||||
syslog, /// messages generated internally by syslogd
|
||||
lpr, /// line printer subsystem
|
||||
news, /// network news subsystem
|
||||
uucp, /// UUCP subsystem
|
||||
clockDaemon, /// clock daemon
|
||||
authpriv, /// security/authorization messages
|
||||
ftp, /// FTP daemon
|
||||
ntp, /// NTP subsystem
|
||||
logAudit, /// log audit
|
||||
logAlert, /// log alert
|
||||
cron, /// clock daemon
|
||||
local0, /// local use 0
|
||||
local1, /// local use 1
|
||||
local2, /// local use 2
|
||||
local3, /// local use 3
|
||||
local4, /// local use 4
|
||||
local5, /// local use 5
|
||||
local6, /// local use 6
|
||||
local7, /// local use 7
|
||||
}
|
||||
|
||||
/// Severities
|
||||
private enum Severity {
|
||||
emergency, /// system is unusable
|
||||
alert, /// action must be taken immediately
|
||||
critical, /// critical conditions
|
||||
error, /// error conditions
|
||||
warning, /// warning conditions
|
||||
notice, /// normal but significant condition
|
||||
info, /// informational messages
|
||||
debug_, /// debug-level messages
|
||||
}
|
||||
|
||||
/// syslog message format (version 1)
|
||||
/// see section 6 in RFC 5424
|
||||
private enum SYSLOG_MESSAGE_FORMAT_VERSION1 = "<%.3s>1 %s %.255s %.48s %.128s %.32s %s %s";
|
||||
///
|
||||
private enum NILVALUE = "-";
|
||||
///
|
||||
private enum BOM = x"EFBBBF";
|
||||
|
||||
/**
|
||||
Construct a SyslogLogger.
|
||||
|
||||
The log messages are sent to the given OutputStream stream using the given
|
||||
Facility facility.Optionally the appName and hostName can be set. The
|
||||
appName defaults to null. The hostName defaults to hostName().
|
||||
|
||||
Note that the passed stream's write function must not use logging with
|
||||
a level for that this Logger's acceptsLevel returns true. Because this
|
||||
Logger uses the stream's write function when it logs and would hence
|
||||
log forevermore.
|
||||
*/
|
||||
this(OutputStream stream, Facility facility, string appName = null, string hostName = hostName())
|
||||
{
|
||||
m_hostName = hostName != "" ? hostName : NILVALUE;
|
||||
m_appName = appName != "" ? appName : NILVALUE;
|
||||
m_ostream = stream;
|
||||
m_facility = facility;
|
||||
this.minLevel = LogLevel.debug_;
|
||||
}
|
||||
|
||||
/**
|
||||
Logs the given LogLine msg.
|
||||
|
||||
It uses the msg's time, level, and text field.
|
||||
*/
|
||||
override void beginLine(ref LogLine msg)
|
||||
@trusted { // OutputStream isn't @safe
|
||||
auto tm = msg.time;
|
||||
import core.time;
|
||||
// at most 6 digits for fractional seconds according to RFC
|
||||
static if (is(typeof(tm.fracSecs))) tm.fracSecs = tm.fracSecs.total!"usecs".dur!"usecs";
|
||||
else tm.fracSec = FracSec.from!"usecs"(tm.fracSec.usecs);
|
||||
auto timestamp = tm.toISOExtString();
|
||||
|
||||
Severity syslogSeverity;
|
||||
// map LogLevel to syslog's severity
|
||||
final switch(msg.level) {
|
||||
case LogLevel.none: assert(false);
|
||||
case LogLevel.trace: return;
|
||||
case LogLevel.debugV: return;
|
||||
case LogLevel.debug_: syslogSeverity = Severity.debug_; break;
|
||||
case LogLevel.diagnostic: syslogSeverity = Severity.info; break;
|
||||
case LogLevel.info: syslogSeverity = Severity.notice; break;
|
||||
case LogLevel.warn: syslogSeverity = Severity.warning; break;
|
||||
case LogLevel.error: syslogSeverity = Severity.error; break;
|
||||
case LogLevel.critical: syslogSeverity = Severity.critical; break;
|
||||
case LogLevel.fatal: syslogSeverity = Severity.alert; break;
|
||||
}
|
||||
|
||||
assert(msg.level >= LogLevel.debug_);
|
||||
import std.conv : to; // temporary workaround for issue 1016 (DMD cross-module template overloads error out before second attempted module)
|
||||
auto priVal = m_facility * 8 + syslogSeverity;
|
||||
|
||||
alias procId = NILVALUE;
|
||||
alias msgId = NILVALUE;
|
||||
alias structuredData = NILVALUE;
|
||||
|
||||
auto text = msg.text;
|
||||
import std.format : formattedWrite;
|
||||
import vibe.stream.wrapper : StreamOutputRange;
|
||||
auto str = StreamOutputRange(m_ostream);
|
||||
(&str).formattedWrite(SYSLOG_MESSAGE_FORMAT_VERSION1, priVal,
|
||||
timestamp, m_hostName, BOM ~ m_appName, procId, msgId,
|
||||
structuredData, BOM);
|
||||
}
|
||||
|
||||
override void put(scope const(char)[] text)
|
||||
@trusted {
|
||||
m_ostream.write(text);
|
||||
}
|
||||
|
||||
override void endLine()
|
||||
@trusted {
|
||||
m_ostream.write("\n");
|
||||
m_ostream.flush();
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
import vibe.core.file;
|
||||
auto fstream = createTempFile();
|
||||
auto logger = new SyslogLogger(fstream, Facility.local1, "appname", null);
|
||||
LogLine msg;
|
||||
import std.datetime;
|
||||
import core.thread;
|
||||
static if (is(typeof(SysTime.init.fracSecs))) auto fs = 1.dur!"usecs";
|
||||
else auto fs = FracSec.from!"usecs"(1);
|
||||
msg.time = SysTime(DateTime(0, 1, 1, 0, 0, 0), fs);
|
||||
|
||||
foreach (lvl; [LogLevel.debug_, LogLevel.diagnostic, LogLevel.info, LogLevel.warn, LogLevel.error, LogLevel.critical, LogLevel.fatal]) {
|
||||
msg.level = lvl;
|
||||
logger.beginLine(msg);
|
||||
logger.put("αβγ");
|
||||
logger.endLine();
|
||||
}
|
||||
fstream.close();
|
||||
|
||||
import std.file;
|
||||
import std.string;
|
||||
auto lines = splitLines(readText(fstream.path().toNativeString()), KeepTerminator.yes);
|
||||
assert(lines.length == 7);
|
||||
assert(lines[0] == "<143>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n");
|
||||
assert(lines[1] == "<142>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n");
|
||||
assert(lines[2] == "<141>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n");
|
||||
assert(lines[3] == "<140>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n");
|
||||
assert(lines[4] == "<139>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n");
|
||||
assert(lines[5] == "<138>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n");
|
||||
assert(lines[6] == "<137>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n");
|
||||
removeFile(fstream.path().toNativeString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns: this host's host name.
|
||||
///
|
||||
/// If the host name cannot be determined the function returns null.
|
||||
private string hostName()
|
||||
{
|
||||
string hostName;
|
||||
|
||||
version (Posix) {
|
||||
import core.sys.posix.sys.utsname;
|
||||
utsname name;
|
||||
if (uname(&name)) return hostName;
|
||||
hostName = name.nodename.to!string();
|
||||
|
||||
import std.socket;
|
||||
auto ih = new InternetHost;
|
||||
if (!ih.getHostByName(hostName)) return hostName;
|
||||
hostName = ih.name;
|
||||
}
|
||||
// TODO: determine proper host name on windows
|
||||
|
||||
return hostName;
|
||||
}
|
||||
|
||||
private {
|
||||
__gshared shared(Logger)[] ss_loggers;
|
||||
shared(FileLogger) ss_stdoutLogger;
|
||||
}
|
||||
|
||||
private shared(Logger)[] getLoggers() nothrow @trusted { return ss_loggers; }
|
||||
|
||||
package void initializeLogModule()
|
||||
{
|
||||
version (Windows) {
|
||||
version (VibeWinrtDriver) enum disable_stdout = true;
|
||||
else {
|
||||
enum disable_stdout = false;
|
||||
if (!GetStdHandle(STD_OUTPUT_HANDLE) || !GetStdHandle(STD_ERROR_HANDLE)) return;
|
||||
}
|
||||
} else enum disable_stdout = false;
|
||||
|
||||
static if (!disable_stdout) {
|
||||
ss_stdoutLogger = cast(shared)new FileLogger(stdout, stderr);
|
||||
{
|
||||
auto l = ss_stdoutLogger.lock();
|
||||
l.minLevel = LogLevel.info;
|
||||
l.format = FileLogger.Format.plain;
|
||||
}
|
||||
registerLogger(ss_stdoutLogger);
|
||||
|
||||
bool[4] verbose;
|
||||
version (VibeNoDefaultArgs) {}
|
||||
else {
|
||||
readOption("verbose|v" , &verbose[0], "Enables diagnostic messages (verbosity level 1).");
|
||||
readOption("vverbose|vv", &verbose[1], "Enables debugging output (verbosity level 2).");
|
||||
readOption("vvv" , &verbose[2], "Enables high frequency debugging output (verbosity level 3).");
|
||||
readOption("vvvv" , &verbose[3], "Enables high frequency trace output (verbosity level 4).");
|
||||
}
|
||||
|
||||
foreach_reverse (i, v; verbose)
|
||||
if (v) {
|
||||
setLogFormat(FileLogger.Format.thread);
|
||||
setLogLevel(cast(LogLevel)(LogLevel.diagnostic - i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct LogOutputRange {
|
||||
LogLine info;
|
||||
ScopedLock!Logger* logger;
|
||||
|
||||
@safe:
|
||||
|
||||
this(ref ScopedLock!Logger logger, string file, int line, LogLevel level)
|
||||
{
|
||||
() @trusted { this.logger = &logger; } ();
|
||||
try {
|
||||
() @trusted { this.info.time = Clock.currTime(UTC()); }(); // not @safe as of 2.065
|
||||
//this.info.mod = mod;
|
||||
//this.info.func = func;
|
||||
this.info.file = file;
|
||||
this.info.line = line;
|
||||
this.info.level = level;
|
||||
this.info.thread = () @trusted { return Thread.getThis(); }(); // not @safe as of 2.065
|
||||
this.info.threadID = makeid(this.info.thread);
|
||||
this.info.fiber = () @trusted { return Fiber.getThis(); }(); // not @safe as of 2.065
|
||||
this.info.fiberID = makeid(this.info.fiber);
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
() @trusted { writefln("Error during logging: %s", e.toString()); }(); // not @safe as of 2.065
|
||||
} catch(Exception) {}
|
||||
assert(false, "Exception during logging: "~e.msg);
|
||||
}
|
||||
|
||||
this.logger.beginLine(info);
|
||||
}
|
||||
|
||||
void finalize()
|
||||
{
|
||||
logger.endLine();
|
||||
}
|
||||
|
||||
void put(scope const(char)[] text)
|
||||
{
|
||||
import std.string : indexOf;
|
||||
auto idx = text.indexOf('\n');
|
||||
if (idx >= 0) {
|
||||
logger.put(text[0 .. idx]);
|
||||
logger.endLine();
|
||||
logger.beginLine(info);
|
||||
logger.put(text[idx+1 .. $]);
|
||||
} else logger.put(text);
|
||||
}
|
||||
|
||||
void put(char ch) @trusted { put((&ch)[0 .. 1]); }
|
||||
|
||||
void put(dchar ch)
|
||||
{
|
||||
if (ch < 128) put(cast(char)ch);
|
||||
else {
|
||||
char[4] buf;
|
||||
auto len = std.utf.encode(buf, ch);
|
||||
put(buf[0 .. len]);
|
||||
}
|
||||
}
|
||||
|
||||
private uint makeid(T)(T ptr) @trusted { return (cast(ulong)cast(void*)ptr & 0xFFFFFFFF) ^ (cast(ulong)cast(void*)ptr >> 32); }
|
||||
}
|
||||
|
||||
private version (Windows) {
|
||||
import core.sys.windows.windows;
|
||||
enum STD_OUTPUT_HANDLE = cast(DWORD)-11;
|
||||
enum STD_ERROR_HANDLE = cast(DWORD)-12;
|
||||
extern(System) HANDLE GetStdHandle(DWORD nStdHandle);
|
||||
}
|
||||
|
||||
unittest { // make sure the default logger doesn't allocate/is usable within finalizers
|
||||
bool destroyed = false;
|
||||
|
||||
class Test {
|
||||
~this()
|
||||
{
|
||||
logInfo("logInfo doesn't allocate.");
|
||||
destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto t = new Test;
|
||||
destroy(t);
|
||||
assert(destroyed);
|
||||
}
|
540
source/vibe/core/net.d
Normal file
540
source/vibe/core/net.d
Normal file
|
@ -0,0 +1,540 @@
|
|||
/**
|
||||
TCP/UDP connection and server handling.
|
||||
|
||||
Copyright: © 2012-2016 RejectedSoftware e.K.
|
||||
Authors: Sönke Ludwig
|
||||
License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
|
||||
*/
|
||||
module vibe.core.net;
|
||||
|
||||
import eventcore.core;
|
||||
import std.exception : enforce;
|
||||
import std.format : format;
|
||||
import std.functional : toDelegate;
|
||||
import std.socket : AddressFamily, UnknownAddress;
|
||||
import vibe.core.log;
|
||||
import vibe.internal.async;
|
||||
|
||||
|
||||
/**
|
||||
Resolves the given host name/IP address string.
|
||||
|
||||
Setting use_dns to false will only allow IP address strings but also guarantees
|
||||
that the call will not block.
|
||||
*/
|
||||
NetworkAddress resolveHost(string host, AddressFamily address_family = AddressFamily.UNSPEC, bool use_dns = true)
|
||||
{
|
||||
return resolveHost(host, cast(ushort)address_family, use_dns);
|
||||
}
|
||||
/// ditto
|
||||
NetworkAddress resolveHost(string host, ushort address_family, bool use_dns = true)
|
||||
{
|
||||
NetworkAddress ret;
|
||||
ret.family = address_family;
|
||||
if (host == "127.0.0.1") {
|
||||
ret.family = AddressFamily.INET;
|
||||
ret.sockAddrInet4.sin_addr.s_addr = 0x0100007F;
|
||||
} else assert(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Starts listening on the specified port.
|
||||
|
||||
'connection_callback' will be called for each client that connects to the
|
||||
server socket. Each new connection gets its own fiber. The stream parameter
|
||||
then allows to perform blocking I/O on the client socket.
|
||||
|
||||
The address parameter can be used to specify the network
|
||||
interface on which the server socket is supposed to listen for connections.
|
||||
By default, all IPv4 and IPv6 interfaces will be used.
|
||||
*/
|
||||
TCPListener[] listenTCP(ushort port, TCPConnectionDelegate connection_callback, TCPListenOptions options = TCPListenOptions.defaults)
|
||||
{
|
||||
TCPListener[] ret;
|
||||
try ret ~= listenTCP(port, connection_callback, "::", options);
|
||||
catch (Exception e) logDiagnostic("Failed to listen on \"::\": %s", e.msg);
|
||||
try ret ~= listenTCP(port, connection_callback, "0.0.0.0", options);
|
||||
catch (Exception e) logDiagnostic("Failed to listen on \"0.0.0.0\": %s", e.msg);
|
||||
enforce(ret.length > 0, format("Failed to listen on all interfaces on port %s", port));
|
||||
return ret;
|
||||
}
|
||||
/// ditto
|
||||
TCPListener listenTCP(ushort port, TCPConnectionDelegate connection_callback, string address, TCPListenOptions options = TCPListenOptions.defaults)
|
||||
{
|
||||
auto addr = resolveHost(address);
|
||||
addr.port = port;
|
||||
auto sock = eventDriver.listenStream(addr.toUnknownAddress, (StreamListenSocketFD ls, StreamSocketFD s) @safe nothrow {
|
||||
import vibe.core.core : runTask;
|
||||
runTask(connection_callback, TCPConnection(s));
|
||||
});
|
||||
return TCPListener(sock);
|
||||
}
|
||||
|
||||
/**
|
||||
Starts listening on the specified port.
|
||||
|
||||
This function is the same as listenTCP but takes a function callback instead of a delegate.
|
||||
*/
|
||||
TCPListener[] listenTCP_s(ushort port, TCPConnectionFunction connection_callback, TCPListenOptions options = TCPListenOptions.defaults)
|
||||
{
|
||||
return listenTCP(port, toDelegate(connection_callback), options);
|
||||
}
|
||||
/// ditto
|
||||
TCPListener listenTCP_s(ushort port, TCPConnectionFunction connection_callback, string address, TCPListenOptions options = TCPListenOptions.defaults)
|
||||
{
|
||||
return listenTCP(port, toDelegate(connection_callback), address, options);
|
||||
}
|
||||
|
||||
/**
|
||||
Establishes a connection to the given host/port.
|
||||
*/
|
||||
TCPConnection connectTCP(string host, ushort port)
|
||||
{
|
||||
NetworkAddress addr = resolveHost(host);
|
||||
addr.port = port;
|
||||
return connectTCP(addr);
|
||||
}
|
||||
/// ditto
|
||||
TCPConnection connectTCP(NetworkAddress addr)
|
||||
{
|
||||
import std.conv : to;
|
||||
|
||||
scope uaddr = new UnknownAddress;
|
||||
addr.toUnknownAddress(uaddr);
|
||||
auto result = eventDriver.asyncAwait!"connectStream"(uaddr);
|
||||
enforce(result[1] == ConnectStatus.connected, "Failed to connect to "~addr.toString()~": "~result[1].to!string);
|
||||
return TCPConnection(result[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Creates a bound UDP socket suitable for sending and receiving packets.
|
||||
*/
|
||||
UDPConnection listenUDP(ushort port, string bind_address = "0.0.0.0")
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/// Callback invoked for incoming TCP connections.
|
||||
@safe nothrow alias TCPConnectionDelegate = void delegate(TCPConnection stream);
|
||||
/// ditto
|
||||
@safe nothrow alias TCPConnectionFunction = void delegate(TCPConnection stream);
|
||||
|
||||
|
||||
/**
|
||||
Represents a network/socket address.
|
||||
*/
|
||||
struct NetworkAddress {
|
||||
version (Windows) import std.c.windows.winsock;
|
||||
else import core.sys.posix.netinet.in_;
|
||||
|
||||
@safe:
|
||||
|
||||
private union {
|
||||
sockaddr addr;
|
||||
sockaddr_in addr_ip4;
|
||||
sockaddr_in6 addr_ip6;
|
||||
}
|
||||
|
||||
/** Family of the socket address.
|
||||
*/
|
||||
@property ushort family() const pure nothrow { return addr.sa_family; }
|
||||
/// ditto
|
||||
@property void family(AddressFamily val) pure nothrow { addr.sa_family = cast(ubyte)val; }
|
||||
/// ditto
|
||||
@property void family(ushort val) pure nothrow { addr.sa_family = cast(ubyte)val; }
|
||||
|
||||
/** The port in host byte order.
|
||||
*/
|
||||
@property ushort port()
|
||||
const pure nothrow {
|
||||
ushort nport;
|
||||
switch (this.family) {
|
||||
default: assert(false, "port() called for invalid address family.");
|
||||
case AF_INET: nport = addr_ip4.sin_port; break;
|
||||
case AF_INET6: nport = addr_ip6.sin6_port; break;
|
||||
}
|
||||
return () @trusted { return ntoh(nport); } ();
|
||||
}
|
||||
/// ditto
|
||||
@property void port(ushort val)
|
||||
pure nothrow {
|
||||
auto nport = () @trusted { return hton(val); } ();
|
||||
switch (this.family) {
|
||||
default: assert(false, "port() called for invalid address family.");
|
||||
case AF_INET: addr_ip4.sin_port = nport; break;
|
||||
case AF_INET6: addr_ip6.sin6_port = nport; break;
|
||||
}
|
||||
}
|
||||
|
||||
/** A pointer to a sockaddr struct suitable for passing to socket functions.
|
||||
*/
|
||||
@property inout(sockaddr)* sockAddr() inout pure nothrow { return &addr; }
|
||||
|
||||
/** Size of the sockaddr struct that is returned by sockAddr().
|
||||
*/
|
||||
@property int sockAddrLen()
|
||||
const pure nothrow {
|
||||
switch (this.family) {
|
||||
default: assert(false, "sockAddrLen() called for invalid address family.");
|
||||
case AF_INET: return addr_ip4.sizeof;
|
||||
case AF_INET6: return addr_ip6.sizeof;
|
||||
}
|
||||
}
|
||||
|
||||
@property inout(sockaddr_in)* sockAddrInet4() inout pure nothrow
|
||||
in { assert (family == AF_INET); }
|
||||
body { return &addr_ip4; }
|
||||
|
||||
@property inout(sockaddr_in6)* sockAddrInet6() inout pure nothrow
|
||||
in { assert (family == AF_INET6); }
|
||||
body { return &addr_ip6; }
|
||||
|
||||
/** Returns a string representation of the IP address
|
||||
*/
|
||||
string toAddressString()
|
||||
const {
|
||||
import std.array : appender;
|
||||
import std.string : format;
|
||||
import std.format : formattedWrite;
|
||||
ubyte[2] _dummy = void; // Workaround for DMD regression in master
|
||||
|
||||
switch (this.family) {
|
||||
default: assert(false, "toAddressString() called for invalid address family.");
|
||||
case AF_INET:
|
||||
ubyte[4] ip = () @trusted { return (cast(ubyte*)&addr_ip4.sin_addr.s_addr)[0 .. 4]; } ();
|
||||
return format("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
||||
case AF_INET6:
|
||||
ubyte[16] ip = addr_ip6.sin6_addr.s6_addr;
|
||||
auto ret = appender!string();
|
||||
ret.reserve(40);
|
||||
foreach (i; 0 .. 8) {
|
||||
if (i > 0) ret.put(':');
|
||||
_dummy[] = ip[i*2 .. i*2+2];
|
||||
ret.formattedWrite("%x", bigEndianToNative!ushort(_dummy));
|
||||
}
|
||||
return ret.data;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a full string representation of the address, including the port number.
|
||||
*/
|
||||
string toString()
|
||||
const {
|
||||
auto ret = toAddressString();
|
||||
switch (this.family) {
|
||||
default: assert(false, "toString() called for invalid address family.");
|
||||
case AF_INET: return ret ~ format(":%s", port);
|
||||
case AF_INET6: return format("[%s]:%s", ret, port);
|
||||
}
|
||||
}
|
||||
|
||||
UnknownAddress toUnknownAddress()
|
||||
const {
|
||||
auto ret = new UnknownAddress;
|
||||
toUnknownAddress(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void toUnknownAddress(scope UnknownAddress addr)
|
||||
const {
|
||||
*addr.name = *this.sockAddr;
|
||||
}
|
||||
|
||||
version(Have_libev) {}
|
||||
else {
|
||||
unittest {
|
||||
void test(string ip) {
|
||||
auto res = () @trusted { return resolveHost(ip, AF_UNSPEC, false); } ().toAddressString();
|
||||
assert(res == ip,
|
||||
"IP "~ip~" yielded wrong string representation: "~res);
|
||||
}
|
||||
test("1.2.3.4");
|
||||
test("102:304:506:708:90a:b0c:d0e:f10");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Represents a single TCP connection.
|
||||
*/
|
||||
struct TCPConnection {
|
||||
@safe:
|
||||
|
||||
import core.time : seconds;
|
||||
import vibe.internal.array : FixedRingBuffer;
|
||||
//static assert(isConnectionStream!TCPConnection);
|
||||
|
||||
struct Context {
|
||||
FixedRingBuffer!ubyte readBuffer;
|
||||
}
|
||||
|
||||
private {
|
||||
StreamSocketFD m_socket;
|
||||
Context* m_context;
|
||||
}
|
||||
|
||||
private this(StreamSocketFD socket)
|
||||
nothrow {
|
||||
m_socket = socket;
|
||||
m_context = &eventDriver.userData!Context(socket);
|
||||
m_context.readBuffer.capacity = 4096;
|
||||
}
|
||||
|
||||
this(this)
|
||||
nothrow {
|
||||
if (m_socket != StreamSocketFD.invalid)
|
||||
eventDriver.addRef(m_socket);
|
||||
}
|
||||
|
||||
~this()
|
||||
nothrow {
|
||||
if (m_socket != StreamSocketFD.invalid)
|
||||
eventDriver.releaseRef(m_socket);
|
||||
}
|
||||
|
||||
@property void tcpNoDelay(bool enabled) { eventDriver.setTCPNoDelay(m_socket, enabled); }
|
||||
@property bool tcpNoDelay() const { assert(false); }
|
||||
@property void keepAlive(bool enable) { assert(false); }
|
||||
@property bool keepAlive() const { assert(false); }
|
||||
@property void readTimeout(Duration duration) { }
|
||||
@property Duration readTimeout() const { assert(false); }
|
||||
@property string peerAddress() const { return ""; }
|
||||
@property NetworkAddress localAddress() const { return NetworkAddress.init; }
|
||||
@property NetworkAddress remoteAddress() const { return NetworkAddress.init; }
|
||||
@property bool connected()
|
||||
const {
|
||||
if (m_socket == StreamSocketFD.invalid) return false;
|
||||
auto s = eventDriver.getConnectionState(m_socket);
|
||||
return s >= ConnectionState.connected && s < ConnectionState.activeClose;
|
||||
}
|
||||
@property bool empty() { return leastSize == 0; }
|
||||
@property ulong leastSize() { waitForData(); return m_context.readBuffer.length; }
|
||||
@property bool dataAvailableForRead() { return waitForData(0.seconds); }
|
||||
|
||||
void close()
|
||||
nothrow {
|
||||
//logInfo("close %s", cast(int)m_fd);
|
||||
if (m_socket != StreamSocketFD.invalid) {
|
||||
eventDriver.shutdownSocket(m_socket);
|
||||
eventDriver.releaseRef(m_socket);
|
||||
m_socket = StreamSocketFD.invalid;
|
||||
m_context = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool waitForData(Duration timeout = Duration.max)
|
||||
{
|
||||
mixin(tracer);
|
||||
// TODO: timeout!!
|
||||
if (m_context.readBuffer.length > 0) return true;
|
||||
auto mode = timeout <= 0.seconds ? IOMode.immediate : IOMode.once;
|
||||
auto res = eventDriver.asyncAwait!"readSocket"(m_socket, m_context.readBuffer.peekDst(), mode);
|
||||
logTrace("Socket %s, read %s bytes: %s", res[0], res[2], res[1]);
|
||||
|
||||
assert(m_context.readBuffer.length == 0);
|
||||
m_context.readBuffer.putN(res[2]);
|
||||
switch (res[1]) {
|
||||
default:
|
||||
logInfo("read status %s", res[1]);
|
||||
throw new Exception("Error reading data from socket.");
|
||||
case IOStatus.ok: break;
|
||||
case IOStatus.wouldBlock: assert(mode == IOMode.immediate); break;
|
||||
case IOStatus.disconnected: break;
|
||||
}
|
||||
|
||||
return m_context.readBuffer.length > 0;
|
||||
}
|
||||
|
||||
const(ubyte)[] peek() { return m_context.readBuffer.peek(); }
|
||||
|
||||
void skip(ulong count)
|
||||
{
|
||||
import std.algorithm.comparison : min;
|
||||
|
||||
while (count > 0) {
|
||||
waitForData();
|
||||
auto n = min(count, m_context.readBuffer.length);
|
||||
m_context.readBuffer.popFrontN(n);
|
||||
if (m_context.readBuffer.empty) m_context.readBuffer.clear(); // start filling at index 0 again
|
||||
count -= n;
|
||||
}
|
||||
}
|
||||
|
||||
void read(ubyte[] dst)
|
||||
{
|
||||
mixin(tracer);
|
||||
import std.algorithm.comparison : min;
|
||||
while (dst.length > 0) {
|
||||
enforce(waitForData(), "Reached end of stream while reading data.");
|
||||
assert(m_context.readBuffer.length > 0);
|
||||
auto l = min(dst.length, m_context.readBuffer.length);
|
||||
m_context.readBuffer.read(dst[0 .. l]);
|
||||
if (m_context.readBuffer.empty) m_context.readBuffer.clear(); // start filling at index 0 again
|
||||
dst = dst[l .. $];
|
||||
}
|
||||
}
|
||||
|
||||
void write(in ubyte[] bytes)
|
||||
{
|
||||
mixin(tracer);
|
||||
if (bytes.length == 0) return;
|
||||
|
||||
auto res = eventDriver.asyncAwait!"writeSocket"(m_socket, bytes, IOMode.all);
|
||||
|
||||
switch (res[1]) {
|
||||
default:
|
||||
throw new Exception("Error writing data to socket.");
|
||||
case IOStatus.ok: break;
|
||||
case IOStatus.disconnected: break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void flush() {
|
||||
mixin(tracer);
|
||||
}
|
||||
void finalize() {}
|
||||
void write(InputStream)(InputStream stream, ulong nbytes = 0) { writeDefault(stream, nbytes); }
|
||||
|
||||
private void writeDefault(InputStream)(InputStream stream, ulong nbytes = 0)
|
||||
{
|
||||
import std.algorithm.comparison : min;
|
||||
|
||||
static struct Buffer { ubyte[64*1024 - 4*size_t.sizeof] bytes = void; }
|
||||
scope bufferobj = new Buffer; // FIXME: use heap allocation
|
||||
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]);
|
||||
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]);
|
||||
write(buffer[0 .. chunk]);
|
||||
nbytes -= chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Represents a listening TCP socket.
|
||||
*/
|
||||
struct TCPListener {
|
||||
private {
|
||||
StreamListenSocketFD m_socket;
|
||||
}
|
||||
|
||||
this(StreamListenSocketFD socket)
|
||||
{
|
||||
m_socket = socket;
|
||||
}
|
||||
|
||||
/// The local address at which TCP connections are accepted.
|
||||
@property NetworkAddress bindAddress()
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/// Stops listening and closes the socket.
|
||||
void stopListening()
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Represents a bound and possibly 'connected' UDP socket.
|
||||
*/
|
||||
struct UDPConnection {
|
||||
/** Returns the address to which the UDP socket is bound.
|
||||
*/
|
||||
@property string bindAddress() const { assert(false); }
|
||||
|
||||
/** Determines if the socket is allowed to send to broadcast addresses.
|
||||
*/
|
||||
@property bool canBroadcast() const { assert(false); }
|
||||
/// ditto
|
||||
@property void canBroadcast(bool val) { assert(false); }
|
||||
|
||||
/// The local/bind address of the underlying socket.
|
||||
@property NetworkAddress localAddress() const { assert(false); }
|
||||
|
||||
/** Stops listening for datagrams and frees all resources.
|
||||
*/
|
||||
void close() { assert(false); }
|
||||
|
||||
/** Locks the UDP connection to a certain peer.
|
||||
|
||||
Once connected, the UDPConnection can only communicate with the specified peer.
|
||||
Otherwise communication with any reachable peer is possible.
|
||||
*/
|
||||
void connect(string host, ushort port) { assert(false); }
|
||||
/// ditto
|
||||
void connect(NetworkAddress address) { assert(false); }
|
||||
|
||||
/** Sends a single packet.
|
||||
|
||||
If peer_address is given, the packet is send to that address. Otherwise the packet
|
||||
will be sent to the address specified by a call to connect().
|
||||
*/
|
||||
void send(in ubyte[] data, in NetworkAddress* peer_address = null) { assert(false); }
|
||||
|
||||
/** Receives a single packet.
|
||||
|
||||
If a buffer is given, it must be large enough to hold the full packet.
|
||||
|
||||
The timeout overload will throw an Exception if no data arrives before the
|
||||
specified duration has elapsed.
|
||||
*/
|
||||
ubyte[] recv(ubyte[] buf = null, NetworkAddress* peer_address = null) { assert(false); }
|
||||
/// ditto
|
||||
ubyte[] recv(Duration timeout, ubyte[] buf = null, NetworkAddress* peer_address = null) { assert(false); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Flags to control the behavior of listenTCP.
|
||||
*/
|
||||
enum TCPListenOptions {
|
||||
/// Don't enable any particular option
|
||||
defaults = 0,
|
||||
/// Causes incoming connections to be distributed across the thread pool
|
||||
distribute = 1<<0,
|
||||
/// Disables automatic closing of the connection when the connection callback exits
|
||||
disableAutoClose = 1<<1,
|
||||
}
|
||||
|
||||
private pure nothrow {
|
||||
import std.bitmanip;
|
||||
|
||||
ushort ntoh(ushort val)
|
||||
{
|
||||
version (LittleEndian) return swapEndian(val);
|
||||
else version (BigEndian) return val;
|
||||
else static assert(false, "Unknown endianness.");
|
||||
}
|
||||
|
||||
ushort hton(ushort val)
|
||||
{
|
||||
version (LittleEndian) return swapEndian(val);
|
||||
else version (BigEndian) return val;
|
||||
else static assert(false, "Unknown endianness.");
|
||||
}
|
||||
}
|
||||
|
||||
private enum tracer = "";
|
25
source/vibe/core/path.d
Normal file
25
source/vibe/core/path.d
Normal file
|
@ -0,0 +1,25 @@
|
|||
module vibe.core.path;
|
||||
|
||||
struct Path {
|
||||
nothrow: @safe:
|
||||
private string m_path;
|
||||
|
||||
this(string p)
|
||||
{
|
||||
m_path = p;
|
||||
}
|
||||
|
||||
string toString() const { return m_path; }
|
||||
|
||||
string toNativeString() const { return m_path; }
|
||||
}
|
||||
|
||||
struct PathEntry {
|
||||
nothrow: @safe:
|
||||
private string m_name;
|
||||
|
||||
this(string name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
}
|
1346
source/vibe/core/sync.d
Normal file
1346
source/vibe/core/sync.d
Normal file
File diff suppressed because it is too large
Load diff
153
source/vibe/core/task.d
Normal file
153
source/vibe/core/task.d
Normal file
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
Contains interfaces and enums for evented I/O drivers.
|
||||
|
||||
Copyright: © 2012-2016 RejectedSoftware e.K.
|
||||
Authors: Sönke Ludwig
|
||||
License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
|
||||
*/
|
||||
module vibe.core.task;
|
||||
|
||||
import vibe.core.sync;
|
||||
import vibe.internal.array : FixedRingBuffer;
|
||||
|
||||
import core.thread;
|
||||
import std.exception;
|
||||
import std.traits;
|
||||
import std.typecons;
|
||||
import std.variant;
|
||||
|
||||
|
||||
/** Represents a single task as started using vibe.core.runTask.
|
||||
|
||||
Note that the Task type is considered weakly isolated and thus can be
|
||||
passed between threads using vibe.core.concurrency.send or by passing
|
||||
it as a parameter to vibe.core.core.runWorkerTask.
|
||||
*/
|
||||
struct Task {
|
||||
private {
|
||||
shared(TaskFiber) m_fiber;
|
||||
size_t m_taskCounter;
|
||||
import std.concurrency : ThreadInfo, Tid;
|
||||
static ThreadInfo s_tidInfo;
|
||||
}
|
||||
|
||||
private this(TaskFiber fiber, size_t task_counter)
|
||||
@safe nothrow {
|
||||
() @trusted { m_fiber = cast(shared)fiber; } ();
|
||||
m_taskCounter = task_counter;
|
||||
}
|
||||
|
||||
this(in Task other) nothrow { m_fiber = cast(shared(TaskFiber))other.m_fiber; m_taskCounter = other.m_taskCounter; }
|
||||
|
||||
/** Returns the Task instance belonging to the calling task.
|
||||
*/
|
||||
static Task getThis() nothrow @safe
|
||||
{
|
||||
// In 2067, synchronized statements where annotated nothrow.
|
||||
// DMD#4115, Druntime#1013, Druntime#1021, Phobos#2704
|
||||
// However, they were "logically" nothrow before.
|
||||
static if (__VERSION__ <= 2066)
|
||||
scope (failure) assert(0, "Internal error: function should be nothrow");
|
||||
|
||||
auto fiber = () @trusted { return Fiber.getThis(); } ();
|
||||
if (!fiber) return Task.init;
|
||||
auto tfiber = cast(TaskFiber)fiber;
|
||||
assert(tfiber !is null, "Invalid or null fiber used to construct Task handle.");
|
||||
if (!tfiber.m_running) return Task.init;
|
||||
return () @trusted { return Task(tfiber, tfiber.m_taskCounter); } ();
|
||||
}
|
||||
|
||||
nothrow {
|
||||
@property inout(TaskFiber) fiber() inout @trusted { return cast(inout(TaskFiber))m_fiber; }
|
||||
@property size_t taskCounter() const @safe { return m_taskCounter; }
|
||||
@property inout(Thread) thread() inout @safe { if (m_fiber) return this.fiber.thread; return null; }
|
||||
|
||||
/** Determines if the task is still running.
|
||||
*/
|
||||
@property bool running()
|
||||
const @trusted {
|
||||
assert(m_fiber !is null, "Invalid task handle");
|
||||
try if (this.fiber.state == Fiber.State.TERM) return false; catch (Throwable) {}
|
||||
return this.fiber.m_running && this.fiber.m_taskCounter == m_taskCounter;
|
||||
}
|
||||
|
||||
// FIXME: this is not thread safe!
|
||||
@property ref ThreadInfo tidInfo() { return m_fiber ? fiber.tidInfo : s_tidInfo; }
|
||||
@property Tid tid() { return tidInfo.ident; }
|
||||
}
|
||||
|
||||
T opCast(T)() const nothrow if (is(T == bool)) { return m_fiber !is null; }
|
||||
|
||||
void join() { if (running) fiber.join(); }
|
||||
void interrupt() { if (running) fiber.interrupt(); }
|
||||
void terminate() { if (running) fiber.terminate(); }
|
||||
|
||||
string toString() const { import std.string; return format("%s:%s", cast(void*)m_fiber, m_taskCounter); }
|
||||
|
||||
bool opEquals(in ref Task other) const nothrow @safe { return m_fiber is other.m_fiber && m_taskCounter == other.m_taskCounter; }
|
||||
bool opEquals(in Task other) const nothrow @safe { return m_fiber is other.m_fiber && m_taskCounter == other.m_taskCounter; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** The base class for a task aka Fiber.
|
||||
|
||||
This class represents a single task that is executed concurrently
|
||||
with other tasks. Each task is owned by a single thread.
|
||||
*/
|
||||
class TaskFiber : Fiber {
|
||||
private {
|
||||
Thread m_thread;
|
||||
import std.concurrency : ThreadInfo;
|
||||
ThreadInfo m_tidInfo;
|
||||
}
|
||||
|
||||
protected {
|
||||
shared size_t m_taskCounter;
|
||||
shared bool m_running;
|
||||
}
|
||||
|
||||
protected this(void delegate() fun, size_t stack_size)
|
||||
nothrow {
|
||||
super(fun, stack_size);
|
||||
m_thread = Thread.getThis();
|
||||
}
|
||||
|
||||
/** Returns the thread that owns this task.
|
||||
*/
|
||||
@property inout(Thread) thread() inout @safe nothrow { return m_thread; }
|
||||
|
||||
/** Returns the handle of the current Task running on this fiber.
|
||||
*/
|
||||
@property Task task() @safe nothrow { return Task(this, m_taskCounter); }
|
||||
|
||||
@property ref inout(ThreadInfo) tidInfo() inout nothrow { return m_tidInfo; }
|
||||
|
||||
/** Blocks until the task has ended.
|
||||
*/
|
||||
abstract void join();
|
||||
|
||||
/** Throws an InterruptExeption within the task as soon as it calls a blocking function.
|
||||
*/
|
||||
abstract void interrupt();
|
||||
|
||||
/** Terminates the task without notice as soon as it calls a blocking function.
|
||||
*/
|
||||
abstract void terminate();
|
||||
|
||||
void bumpTaskCounter()
|
||||
@safe nothrow {
|
||||
import core.atomic : atomicOp;
|
||||
() @trusted { atomicOp!"+="(this.m_taskCounter, 1); } ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Exception that is thrown by Task.interrupt.
|
||||
*/
|
||||
class InterruptException : Exception {
|
||||
this()
|
||||
{
|
||||
super("Task interrupted.");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue