mirror of https://github.com/adamdruppe/arsd.git
Merge branch 'master' of github.com:adamdruppe/arsd
This commit is contained in:
commit
9cb2c9f539
245
cgi.d
245
cgi.d
|
|
@ -1,12 +1,33 @@
|
||||||
// FIXME: if an exception is thrown, we shouldn't necessarily cache...
|
// FIXME: if an exception is thrown, we shouldn't necessarily cache...
|
||||||
// FIXME: there's some annoying duplication of code in the various versioned mains
|
// FIXME: there's some annoying duplication of code in the various versioned mains
|
||||||
// FIXME: new ConnectionThread is done a lot, no pooling implemented
|
|
||||||
|
|
||||||
// Note: spawn-fcgi can help with fastcgi on nginx
|
// Note: spawn-fcgi can help with fastcgi on nginx
|
||||||
|
|
||||||
// FIXME: to do: add openssl optionally
|
// FIXME: to do: add openssl optionally
|
||||||
// make sure embedded_httpd doesn't send two answers if one writes() then dies
|
// make sure embedded_httpd doesn't send two answers if one writes() then dies
|
||||||
|
|
||||||
|
// future direction: websocket as a separate process that you can sendfile to for an async passoff of those long-lived connections
|
||||||
|
|
||||||
|
/*
|
||||||
|
Session manager process: it spawns a new process, passing a
|
||||||
|
command line argument, to just be a little key/value store
|
||||||
|
of some serializable struct. On Windows, it CreateProcess.
|
||||||
|
On Linux, it can just fork or maybe fork/exec. The session
|
||||||
|
key is in a cookie.
|
||||||
|
|
||||||
|
Server-side event process: spawns an async manager. You can
|
||||||
|
push stuff out to channel ids and the clients listen to it.
|
||||||
|
|
||||||
|
websocket process: spawns an async handler. They can talk to
|
||||||
|
each other or get info from a cgi request.
|
||||||
|
|
||||||
|
Tempting to put web.d 2.0 in here. It would:
|
||||||
|
* map urls and form generation to functions
|
||||||
|
* have data presentation magic
|
||||||
|
* do the skeleton stuff like 1.0
|
||||||
|
* auto-cache generated stuff in files (at least if pure?)
|
||||||
|
*/
|
||||||
|
|
||||||
/++
|
/++
|
||||||
Provides a uniform server-side API for CGI, FastCGI, SCGI, and HTTP web applications.
|
Provides a uniform server-side API for CGI, FastCGI, SCGI, and HTTP web applications.
|
||||||
|
|
||||||
|
|
@ -1518,8 +1539,10 @@ class Cgi {
|
||||||
if(header.indexOf("HTTP/1.0") != -1) {
|
if(header.indexOf("HTTP/1.0") != -1) {
|
||||||
http10 = true;
|
http10 = true;
|
||||||
autoBuffer = true;
|
autoBuffer = true;
|
||||||
if(closeConnection)
|
if(closeConnection) {
|
||||||
|
// on http 1.0, close is assumed (unlike http/1.1 where we assume keep alive)
|
||||||
*closeConnection = true;
|
*closeConnection = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// other header
|
// other header
|
||||||
|
|
@ -1540,8 +1563,16 @@ class Cgi {
|
||||||
else if (name == "connection") {
|
else if (name == "connection") {
|
||||||
if(value == "close" && closeConnection)
|
if(value == "close" && closeConnection)
|
||||||
*closeConnection = true;
|
*closeConnection = true;
|
||||||
if(value.toLower().indexOf("keep-alive") != -1)
|
if(value.toLower().indexOf("keep-alive") != -1) {
|
||||||
keepAliveRequested = true;
|
keepAliveRequested = true;
|
||||||
|
|
||||||
|
// on http 1.0, the connection is closed by default,
|
||||||
|
// but not if they request keep-alive. then we don't close
|
||||||
|
// anymore - undoing the set above
|
||||||
|
if(http10 && closeConnection) {
|
||||||
|
*closeConnection = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (name == "transfer-encoding") {
|
else if (name == "transfer-encoding") {
|
||||||
if(value == "chunked")
|
if(value == "chunked")
|
||||||
|
|
@ -2737,6 +2768,9 @@ mixin template CustomCgiMainImpl(CustomCgi, alias fun, long maxContentLength = d
|
||||||
throw new Exception("bind");
|
throw new Exception("bind");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: if this queue is full, it will just ignore it
|
||||||
|
// and wait for the client to retransmit it. This is an
|
||||||
|
// obnoxious timeout condition there.
|
||||||
if(sock.listen(128) == -1) {
|
if(sock.listen(128) == -1) {
|
||||||
close(sock);
|
close(sock);
|
||||||
throw new Exception("listen");
|
throw new Exception("listen");
|
||||||
|
|
@ -2968,6 +3002,9 @@ void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
|
||||||
sendAll(connection, plainHttpError(false, "500 Internal Server Error", null));
|
sendAll(connection, plainHttpError(false, "500 Internal Server Error", null));
|
||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connection.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
|
||||||
|
|
||||||
bool closeConnection;
|
bool closeConnection;
|
||||||
auto ir = new BufferedInputRange(connection);
|
auto ir = new BufferedInputRange(connection);
|
||||||
|
|
||||||
|
|
@ -3100,6 +3137,7 @@ void doThreadScgiConnection(CustomCgi, alias fun, long maxContentLength)(Socket
|
||||||
try {
|
try {
|
||||||
fun(cgi);
|
fun(cgi);
|
||||||
cgi.close();
|
cgi.close();
|
||||||
|
connection.close();
|
||||||
} catch(Throwable t) {
|
} catch(Throwable t) {
|
||||||
// no std err
|
// no std err
|
||||||
if(!handleException(cgi, t)) {
|
if(!handleException(cgi, t)) {
|
||||||
|
|
@ -3316,6 +3354,11 @@ class BufferedInputRange {
|
||||||
try_again:
|
try_again:
|
||||||
auto ret = source.receive(freeSpace);
|
auto ret = source.receive(freeSpace);
|
||||||
if(ret == Socket.ERROR) {
|
if(ret == Socket.ERROR) {
|
||||||
|
if(wouldHaveBlocked()) {
|
||||||
|
// gonna treat a timeout here as a close
|
||||||
|
sourceClosed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
version(Posix) {
|
version(Posix) {
|
||||||
import core.stdc.errno;
|
import core.stdc.errno;
|
||||||
if(errno == EINTR || errno == EAGAIN) {
|
if(errno == EINTR || errno == EAGAIN) {
|
||||||
|
|
@ -3374,32 +3417,8 @@ class BufferedInputRange {
|
||||||
bool sourceClosed;
|
bool sourceClosed;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConnectionThread2 : Thread {
|
import core.sync.semaphore;
|
||||||
import std.concurrency;
|
import core.atomic;
|
||||||
this(void function(Socket) handler) {
|
|
||||||
this.handler = handler;
|
|
||||||
super(&run);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() {
|
|
||||||
tid = thisTid();
|
|
||||||
available = true;
|
|
||||||
while(true)
|
|
||||||
receive(
|
|
||||||
(/*Socket*/ size_t s) {
|
|
||||||
available = false;
|
|
||||||
try {
|
|
||||||
handler(cast(Socket) cast(void*) s);
|
|
||||||
} catch(Throwable t) {}
|
|
||||||
available = true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool available;
|
|
||||||
Tid tid;
|
|
||||||
void function(Socket) handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
To use this thing:
|
To use this thing:
|
||||||
|
|
@ -3415,38 +3434,61 @@ class ConnectionThread2 : Thread {
|
||||||
FIXME: should I offer an event based async thing like netman did too? Yeah, probably.
|
FIXME: should I offer an event based async thing like netman did too? Yeah, probably.
|
||||||
*/
|
*/
|
||||||
class ListeningConnectionManager {
|
class ListeningConnectionManager {
|
||||||
|
Semaphore semaphore;
|
||||||
|
Socket[256] queue;
|
||||||
|
shared(ubyte) nextIndexFront;
|
||||||
|
ubyte nextIndexBack;
|
||||||
|
shared(int) queueLength;
|
||||||
|
|
||||||
void listen() {
|
void listen() {
|
||||||
version(cgi_multiple_connections_per_thread) {
|
running = true;
|
||||||
import std.concurrency;
|
shared(int) loopBroken;
|
||||||
import std.random;
|
|
||||||
ConnectionThread2[16] pool;
|
|
||||||
foreach(ref p; pool) {
|
|
||||||
p = new ConnectionThread2(handler);
|
|
||||||
p.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
while(true) {
|
version(cgi_no_threads) {
|
||||||
auto connection = listener.accept();
|
// NEVER USE THIS
|
||||||
|
// it exists only for debugging and other special occasions
|
||||||
|
|
||||||
bool handled = false;
|
// the thread mode is faster and less likely to stall the whole
|
||||||
retry:
|
// thing when a request is slow
|
||||||
foreach(p; pool)
|
while(!loopBroken && running) {
|
||||||
if(p.available) {
|
auto sn = listener.accept();
|
||||||
handled = true;
|
try {
|
||||||
send(p.tid, cast(size_t) cast(void*) connection);
|
handler(sn);
|
||||||
break;
|
} catch(Exception e) {
|
||||||
}
|
// if a connection goes wrong, we want to just say no, but try to carry on unless it is an Error of some sort (in which case, we'll die. You might want an external helper program to revive the server when it dies)
|
||||||
|
sn.close();
|
||||||
// none available right now, make it wait a bit then try again
|
|
||||||
if(!handled) {
|
|
||||||
Thread.sleep(dur!"msecs"(25));
|
|
||||||
goto retry;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach(connection; this)
|
semaphore = new Semaphore();
|
||||||
handler(connection);
|
|
||||||
|
ConnectionThread[16] threads;
|
||||||
|
foreach(ref thread; threads) {
|
||||||
|
thread = new ConnectionThread(this, handler);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
while(!loopBroken && running) {
|
||||||
|
auto sn = listener.accept();
|
||||||
|
// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
|
||||||
|
// on the socket because we do some buffering internally. I think this helps,
|
||||||
|
// certainly does for small requests, and I think it does for larger ones too
|
||||||
|
sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
|
||||||
|
while(queueLength >= queue.length)
|
||||||
|
Thread.sleep(1.msecs);
|
||||||
|
synchronized(this) {
|
||||||
|
queue[nextIndexBack] = sn;
|
||||||
|
nextIndexBack++;
|
||||||
|
atomicOp!"+="(queueLength, 1);
|
||||||
|
}
|
||||||
|
semaphore.notify();
|
||||||
|
|
||||||
|
foreach(thread; threads) {
|
||||||
|
if(!thread.isRunning) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3465,50 +3507,6 @@ class ListeningConnectionManager {
|
||||||
void quit() {
|
void quit() {
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int opApply(scope CMT dg) {
|
|
||||||
running = true;
|
|
||||||
shared(int) loopBroken;
|
|
||||||
|
|
||||||
while(!loopBroken && running) {
|
|
||||||
auto sn = listener.accept();
|
|
||||||
try {
|
|
||||||
version(cgi_no_threads) {
|
|
||||||
// NEVER USE THIS
|
|
||||||
// it exists only for debugging and other special occasions
|
|
||||||
|
|
||||||
// the thread mode is faster and less likely to stall the whole
|
|
||||||
// thing when a request is slow
|
|
||||||
dg(sn);
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
version(cgi_multiple_connections_per_thread) {
|
|
||||||
bool foundOne = false;
|
|
||||||
tryAgain:
|
|
||||||
foreach(t; pool)
|
|
||||||
if(t.s is null) {
|
|
||||||
t.s = sn;
|
|
||||||
foundOne = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Thread.sleep(dur!"msecs"(1));
|
|
||||||
if(!foundOne)
|
|
||||||
goto tryAgain;
|
|
||||||
} else {
|
|
||||||
*/
|
|
||||||
auto thread = new ConnectionThread(sn, &loopBroken, dg);
|
|
||||||
thread.start();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
// loopBroken = dg(sn);
|
|
||||||
} catch(Exception e) {
|
|
||||||
// if a connection goes wrong, we want to just say no, but try to carry on unless it is an Error of some sort (in which case, we'll die. You might want an external helper program to revive the server when it dies)
|
|
||||||
sn.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return loopBroken;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to send a lot to a socket. Since this blocks for the buffer (possibly several times), you should probably call it in a separate thread or something.
|
// helper function to send a lot to a socket. Since this blocks for the buffer (possibly several times), you should probably call it in a separate thread or something.
|
||||||
|
|
@ -3532,46 +3530,35 @@ class ConnectionException : Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alias int delegate(Socket) CMT;
|
alias void function(Socket) CMT;
|
||||||
|
|
||||||
import core.thread;
|
import core.thread;
|
||||||
class ConnectionThread : Thread {
|
class ConnectionThread : Thread {
|
||||||
this(Socket s, shared(int)* breakSignifier, CMT dg) {
|
this(ListeningConnectionManager lcm, CMT dg) {
|
||||||
this.s = s;
|
this.lcm = lcm;
|
||||||
this.breakSignifier = breakSignifier;
|
|
||||||
this.dg = dg;
|
this.dg = dg;
|
||||||
super(&runAll);
|
super(&run);
|
||||||
}
|
|
||||||
|
|
||||||
void runAll() {
|
|
||||||
if(s !is null)
|
|
||||||
run();
|
|
||||||
/*
|
|
||||||
version(cgi_multiple_connections_per_thread) {
|
|
||||||
while(1) {
|
|
||||||
while(s is null)
|
|
||||||
sleep(dur!"msecs"(1));
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void run() {
|
void run() {
|
||||||
scope(exit) {
|
while(true) {
|
||||||
// I don't want to double close it, and it does this on close() according to source
|
lcm.semaphore.wait();
|
||||||
// might be fragile, but meh
|
Socket socket;
|
||||||
if(s.handle() != socket_t.init)
|
synchronized(lcm) {
|
||||||
s.close();
|
auto idx = lcm.nextIndexFront;
|
||||||
s = null; // so we know this thread is clear
|
socket = lcm.queue[idx];
|
||||||
}
|
lcm.queue[idx] = null;
|
||||||
if(auto result = dg(s)) {
|
atomicOp!"+="(lcm.nextIndexFront, 1);
|
||||||
*breakSignifier = result;
|
atomicOp!"-="(lcm.queueLength, 1);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
dg(socket);
|
||||||
|
catch(Exception e)
|
||||||
|
socket.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket s;
|
ListeningConnectionManager lcm;
|
||||||
shared(int)* breakSignifier;
|
|
||||||
CMT dg;
|
CMT dg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
color.d
28
color.d
|
|
@ -6,7 +6,7 @@ module arsd.color;
|
||||||
// importing phobos explodes the size of this code 10x, so not doing it.
|
// importing phobos explodes the size of this code 10x, so not doing it.
|
||||||
|
|
||||||
private {
|
private {
|
||||||
real toInternal(T)(string s) {
|
real toInternal(T)(scope const(char)[] s) {
|
||||||
real accumulator = 0.0;
|
real accumulator = 0.0;
|
||||||
size_t i = s.length;
|
size_t i = s.length;
|
||||||
foreach(idx, c; s) {
|
foreach(idx, c; s) {
|
||||||
|
|
@ -16,8 +16,11 @@ private {
|
||||||
} else if(c == '.') {
|
} else if(c == '.') {
|
||||||
i = idx + 1;
|
i = idx + 1;
|
||||||
break;
|
break;
|
||||||
} else
|
} else {
|
||||||
throw new Exception("bad char to make real from " ~ s);
|
string wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute = "bad char to make real from ";
|
||||||
|
wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute ~= s;
|
||||||
|
throw new Exception(wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
real accumulator2 = 0.0;
|
real accumulator2 = 0.0;
|
||||||
|
|
@ -27,8 +30,11 @@ private {
|
||||||
accumulator2 *= 10;
|
accumulator2 *= 10;
|
||||||
accumulator2 += c - '0';
|
accumulator2 += c - '0';
|
||||||
count *= 10;
|
count *= 10;
|
||||||
} else
|
} else {
|
||||||
throw new Exception("bad char to make real from " ~ s);
|
string wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute = "bad char to make real from ";
|
||||||
|
wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute ~= s;
|
||||||
|
throw new Exception(wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return accumulator + accumulator2 / count;
|
return accumulator + accumulator2 / count;
|
||||||
|
|
@ -80,11 +86,11 @@ private {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
nothrow @safe @nogc pure
|
nothrow @safe @nogc pure
|
||||||
bool startsWithInternal(string a, string b) {
|
bool startsWithInternal(in char[] a, in char[] b) {
|
||||||
return (a.length >= b.length && a[0 .. b.length] == b);
|
return (a.length >= b.length && a[0 .. b.length] == b);
|
||||||
}
|
}
|
||||||
string[] splitInternal(string a, char c) {
|
inout(char)[][] splitInternal(inout(char)[] a, char c) {
|
||||||
string[] ret;
|
inout(char)[][] ret;
|
||||||
size_t previous = 0;
|
size_t previous = 0;
|
||||||
foreach(i, char ch; a) {
|
foreach(i, char ch; a) {
|
||||||
if(ch == c) {
|
if(ch == c) {
|
||||||
|
|
@ -97,7 +103,7 @@ private {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
nothrow @safe @nogc pure
|
nothrow @safe @nogc pure
|
||||||
string stripInternal(string s) {
|
inout(char)[] stripInternal(inout(char)[] s) {
|
||||||
foreach(i, char c; s)
|
foreach(i, char c; s)
|
||||||
if(c != ' ' && c != '\t' && c != '\n') {
|
if(c != ' ' && c != '\t' && c != '\n') {
|
||||||
s = s[i .. $];
|
s = s[i .. $];
|
||||||
|
|
@ -242,7 +248,7 @@ struct Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa
|
/// Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa
|
||||||
static Color fromString(string s) {
|
static Color fromString(scope const(char)[] s) {
|
||||||
s = s.stripInternal();
|
s = s.stripInternal();
|
||||||
|
|
||||||
Color c;
|
Color c;
|
||||||
|
|
@ -422,7 +428,7 @@ private string toHexInternal(ubyte b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
nothrow @safe @nogc pure
|
nothrow @safe @nogc pure
|
||||||
private ubyte fromHexInternal(string s) {
|
private ubyte fromHexInternal(in char[] s) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
int exp = 1;
|
int exp = 1;
|
||||||
|
|
|
||||||
4
dom.d
4
dom.d
|
|
@ -4819,7 +4819,7 @@ class Table : Element {
|
||||||
tagName = "table";
|
tagName = "table";
|
||||||
}
|
}
|
||||||
|
|
||||||
///.
|
/// Creates an element with the given type and content.
|
||||||
Element th(T)(T t) {
|
Element th(T)(T t) {
|
||||||
Element e;
|
Element e;
|
||||||
if(parentDocument !is null)
|
if(parentDocument !is null)
|
||||||
|
|
@ -4833,7 +4833,7 @@ class Table : Element {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
///.
|
/// ditto
|
||||||
Element td(T)(T t) {
|
Element td(T)(T t) {
|
||||||
Element e;
|
Element e;
|
||||||
if(parentDocument !is null)
|
if(parentDocument !is null)
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,23 @@ final class OpenGlTexture {
|
||||||
|
|
||||||
/// Make a texture from an image.
|
/// Make a texture from an image.
|
||||||
this(TrueColorImage from) {
|
this(TrueColorImage from) {
|
||||||
|
bindFrom(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates from text. Requires ttf.d
|
||||||
|
/// pass a pointer to the TtfFont as the first arg (it is template cuz of lazy importing, not because it actually works with different types)
|
||||||
|
this(T, FONT)(FONT* font, int size, in T[] text) if(is(T == char)) {
|
||||||
|
bindFrom(font, size, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an empty texture class for you to use with [bindFrom] later
|
||||||
|
/// Using it when not bound is undefined behavior.
|
||||||
|
this() {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// After you delete it with dispose, you may rebind it to something else with this.
|
||||||
|
void bindFrom(TrueColorImage from) {
|
||||||
assert(from.width > 0 && from.height > 0);
|
assert(from.width > 0 && from.height > 0);
|
||||||
|
|
||||||
import core.stdc.stdlib;
|
import core.stdc.stdlib;
|
||||||
|
|
@ -295,9 +312,8 @@ final class OpenGlTexture {
|
||||||
free(cast(void*) data);
|
free(cast(void*) data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates from text. Requires ttf.d
|
/// ditto
|
||||||
/// pass a pointer to the TtfFont as the first arg (it is template cuz of lazy importing, not because it actually works with different types)
|
void bindFrom(T, FONT)(FONT* font, int size, in T[] text) if(is(T == char)) {
|
||||||
this(T, FONT)(FONT* font, int size, in T[] text) if(is(T == char)) {
|
|
||||||
assert(font !is null);
|
assert(font !is null);
|
||||||
int width, height;
|
int width, height;
|
||||||
auto data = font.renderString(text, size, width, height);
|
auto data = font.renderString(text, size, width, height);
|
||||||
|
|
@ -313,14 +329,26 @@ final class OpenGlTexture {
|
||||||
}
|
}
|
||||||
assert(data.length == 0);
|
assert(data.length == 0);
|
||||||
|
|
||||||
this(image);
|
bindFrom(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the texture. Using it after calling this is undefined behavior
|
||||||
|
void dispose() {
|
||||||
|
glDeleteTextures(1, &_tex);
|
||||||
|
_tex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
~this() {
|
~this() {
|
||||||
glDeleteTextures(1, &_tex);
|
if(_tex > 0)
|
||||||
|
dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
void clearOpenGlScreen(SimpleWindow window) {
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Some math helpers
|
// Some math helpers
|
||||||
|
|
||||||
|
|
|
||||||
6
mysql.d
6
mysql.d
|
|
@ -197,10 +197,10 @@ class MySqlResult : ResultSet {
|
||||||
+/
|
+/
|
||||||
class MySql : Database {
|
class MySql : Database {
|
||||||
this(string host, string user, string pass, string db, uint port = 0) {
|
this(string host, string user, string pass, string db, uint port = 0) {
|
||||||
mysql = enforceEx!(DatabaseException)(
|
mysql = enforce!(DatabaseException)(
|
||||||
mysql_init(null),
|
mysql_init(null),
|
||||||
"Couldn't init mysql");
|
"Couldn't init mysql");
|
||||||
enforceEx!(DatabaseException)(
|
enforce!(DatabaseException)(
|
||||||
mysql_real_connect(mysql, toCstring(host), toCstring(user), toCstring(pass), toCstring(db), port, null, 0),
|
mysql_real_connect(mysql, toCstring(host), toCstring(user), toCstring(pass), toCstring(db), port, null, 0),
|
||||||
error());
|
error());
|
||||||
|
|
||||||
|
|
@ -371,7 +371,7 @@ class MySql : Database {
|
||||||
override ResultSet queryImpl(string sql, Variant[] args...) {
|
override ResultSet queryImpl(string sql, Variant[] args...) {
|
||||||
sql = escapedVariants(this, sql, args);
|
sql = escapedVariants(this, sql, args);
|
||||||
|
|
||||||
enforceEx!(DatabaseException)(
|
enforce!(DatabaseException)(
|
||||||
!mysql_query(mysql, toCstring(sql)),
|
!mysql_query(mysql, toCstring(sql)),
|
||||||
error() ~ " :::: " ~ sql);
|
error() ~ " :::: " ~ sql);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9674,6 +9674,9 @@ version(X11) {
|
||||||
scope(exit) XLockDisplay(display);
|
scope(exit) XLockDisplay(display);
|
||||||
win.setSelectionHandler(e);
|
win.setSelectionHandler(e);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case EventType.PropertyNotify:
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case EventType.SelectionNotify:
|
case EventType.SelectionNotify:
|
||||||
if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
|
if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping)
|
||||||
|
|
@ -9694,11 +9697,11 @@ version(X11) {
|
||||||
e.xselection.property,
|
e.xselection.property,
|
||||||
0,
|
0,
|
||||||
100000 /* length */,
|
100000 /* length */,
|
||||||
false,
|
//false, /* don't erase it */
|
||||||
|
true, /* do erase it lol */
|
||||||
0 /*AnyPropertyType*/,
|
0 /*AnyPropertyType*/,
|
||||||
&target, &format, &length, &bytesafter, &value);
|
&target, &format, &length, &bytesafter, &value);
|
||||||
|
|
||||||
// FIXME: it might be sent in pieces...
|
|
||||||
// FIXME: I don't have to copy it now since it is in char[] instead of string
|
// FIXME: I don't have to copy it now since it is in char[] instead of string
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -9728,15 +9731,51 @@ version(X11) {
|
||||||
}
|
}
|
||||||
} else if(target == GetAtom!"UTF8_STRING"(display) || target == XA_STRING) {
|
} else if(target == GetAtom!"UTF8_STRING"(display) || target == XA_STRING) {
|
||||||
win.getSelectionHandler((cast(char[]) value[0 .. length]).idup);
|
win.getSelectionHandler((cast(char[]) value[0 .. length]).idup);
|
||||||
|
} else if(target == GetAtom!"INCR"(display)) {
|
||||||
|
// incremental
|
||||||
|
|
||||||
|
//sdpyGettingPaste = true; // FIXME: should prolly be separate for the different selections
|
||||||
|
|
||||||
|
// FIXME: handle other events while it goes so app doesn't lock up with big pastes
|
||||||
|
// can also be optimized if it chunks to the api somehow
|
||||||
|
|
||||||
|
char[] s;
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
XEvent subevent;
|
||||||
|
do {
|
||||||
|
XMaskEvent(display, EventMask.PropertyChangeMask, &subevent);
|
||||||
|
} while(subevent.type != EventType.PropertyNotify || subevent.xproperty.atom != e.xselection.property || subevent.xproperty.state != PropertyNotification.PropertyNewValue);
|
||||||
|
|
||||||
|
void* subvalue;
|
||||||
|
XGetWindowProperty(
|
||||||
|
e.xselection.display,
|
||||||
|
e.xselection.requestor,
|
||||||
|
e.xselection.property,
|
||||||
|
0,
|
||||||
|
100000 /* length */,
|
||||||
|
true, /* erase it to signal we got it and want more */
|
||||||
|
0 /*AnyPropertyType*/,
|
||||||
|
&target, &format, &length, &bytesafter, &subvalue);
|
||||||
|
|
||||||
|
s ~= (cast(char*) subvalue)[0 .. length];
|
||||||
|
|
||||||
|
XFree(subvalue);
|
||||||
|
} while(length > 0);
|
||||||
|
|
||||||
|
win.getSelectionHandler(s);
|
||||||
} else {
|
} else {
|
||||||
// unsupported type
|
// unsupported type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
XFree(value);
|
XFree(value);
|
||||||
|
/*
|
||||||
XDeleteProperty(
|
XDeleteProperty(
|
||||||
e.xselection.display,
|
e.xselection.display,
|
||||||
e.xselection.requestor,
|
e.xselection.requestor,
|
||||||
e.xselection.property);
|
e.xselection.property);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -10853,6 +10892,8 @@ int XNextEvent(
|
||||||
XEvent* /* event_return */
|
XEvent* /* event_return */
|
||||||
);
|
);
|
||||||
|
|
||||||
|
int XMaskEvent(Display*, arch_long, XEvent*);
|
||||||
|
|
||||||
Bool XFilterEvent(XEvent *event, Window window);
|
Bool XFilterEvent(XEvent *event, Window window);
|
||||||
int XRefreshKeyboardMapping(XMappingEvent *event_map);
|
int XRefreshKeyboardMapping(XMappingEvent *event_map);
|
||||||
|
|
||||||
|
|
|
||||||
1
ttf.d
1
ttf.d
|
|
@ -92,6 +92,7 @@ struct TtfFont {
|
||||||
stbtt_GetCodepointHMetrics(&font, ch, &advance, &lsb);
|
stbtt_GetCodepointHMetrics(&font, ch, &advance, &lsb);
|
||||||
int cw, cheight;
|
int cw, cheight;
|
||||||
auto c = renderCharacter(ch, size, cw, cheight, x_shift, 0.0);
|
auto c = renderCharacter(ch, size, cw, cheight, x_shift, 0.0);
|
||||||
|
scope(exit) stbtt_FreeBitmap(c.ptr, null);
|
||||||
|
|
||||||
int x0, y0, x1, y1;
|
int x0, y0, x1, y1;
|
||||||
stbtt_GetCodepointBitmapBoxSubpixel(&font, ch, scale,scale,x_shift,0, &x0,&y0,&x1,&y1);
|
stbtt_GetCodepointBitmapBoxSubpixel(&font, ch, scale,scale,x_shift,0, &x0,&y0,&x1,&y1);
|
||||||
|
|
|
||||||
56
web.d
56
web.d
|
|
@ -9,6 +9,11 @@ enum RequirePost;
|
||||||
enum RequireHttps;
|
enum RequireHttps;
|
||||||
enum NoAutomaticForm;
|
enum NoAutomaticForm;
|
||||||
|
|
||||||
|
///
|
||||||
|
struct GenericContainerType {
|
||||||
|
string type; ///
|
||||||
|
}
|
||||||
|
|
||||||
/// Attribute for the default formatting (html, table, json, etc)
|
/// Attribute for the default formatting (html, table, json, etc)
|
||||||
struct DefaultFormat {
|
struct DefaultFormat {
|
||||||
string format;
|
string format;
|
||||||
|
|
@ -577,7 +582,7 @@ class ApiProvider : WebDotDBaseType {
|
||||||
/// Returns a list of links to all functions in this class or sub-classes
|
/// Returns a list of links to all functions in this class or sub-classes
|
||||||
/// You can expose it publicly with alias: "alias _sitemap sitemap;" for example.
|
/// You can expose it publicly with alias: "alias _sitemap sitemap;" for example.
|
||||||
Element _sitemap() {
|
Element _sitemap() {
|
||||||
auto container = _getGenericContainer();
|
auto container = Element.make("div", "", "sitemap");
|
||||||
|
|
||||||
void writeFunctions(Element list, in ReflectionInfo* reflection, string base) {
|
void writeFunctions(Element list, in ReflectionInfo* reflection, string base) {
|
||||||
string[string] handled;
|
string[string] handled;
|
||||||
|
|
@ -617,7 +622,7 @@ class ApiProvider : WebDotDBaseType {
|
||||||
starting = cgi.logicalScriptName ~ cgi.pathInfo; // FIXME
|
starting = cgi.logicalScriptName ~ cgi.pathInfo; // FIXME
|
||||||
writeFunctions(list, reflection, starting ~ "/");
|
writeFunctions(list, reflection, starting ~ "/");
|
||||||
|
|
||||||
return list.parentNode.removeChild(list);
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the user goes to your program without specifying a path, this function is called.
|
/// If the user goes to your program without specifying a path, this function is called.
|
||||||
|
|
@ -626,13 +631,18 @@ class ApiProvider : WebDotDBaseType {
|
||||||
throw new Exception("no default");
|
throw new Exception("no default");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// forwards to [_getGenericContainer]("default")
|
||||||
|
Element _getGenericContainer() {
|
||||||
|
return _getGenericContainer("default");
|
||||||
|
}
|
||||||
|
|
||||||
/// When the html document envelope is used, this function is used to get a html element
|
/// When the html document envelope is used, this function is used to get a html element
|
||||||
/// where the return value is appended.
|
/// where the return value is appended.
|
||||||
|
|
||||||
/// It's the main function to override to provide custom HTML templates.
|
/// It's the main function to override to provide custom HTML templates.
|
||||||
///
|
///
|
||||||
/// The default document provides a default stylesheet, our default javascript, and some timezone cookie handling (which you must handle on the server. Eventually I'll open source my date-time helpers that do this, but the basic idea is it sends an hour offset, and you can add that to any UTC time you have to get a local time).
|
/// The default document provides a default stylesheet, our default javascript, and some timezone cookie handling (which you must handle on the server. Eventually I'll open source my date-time helpers that do this, but the basic idea is it sends an hour offset, and you can add that to any UTC time you have to get a local time).
|
||||||
Element _getGenericContainer()
|
Element _getGenericContainer(string containerName)
|
||||||
out(ret) {
|
out(ret) {
|
||||||
assert(ret !is null);
|
assert(ret !is null);
|
||||||
}
|
}
|
||||||
|
|
@ -766,7 +776,7 @@ struct ReflectionInfo {
|
||||||
|
|
||||||
// these might go away.
|
// these might go away.
|
||||||
|
|
||||||
string defaultOutputFormat = "html";
|
string defaultOutputFormat = "default";
|
||||||
int versionOfOutputFormat = 2; // change this in your constructor if you still need the (deprecated) old behavior
|
int versionOfOutputFormat = 2; // change this in your constructor if you still need the (deprecated) old behavior
|
||||||
// bool apiMode = false; // no longer used - if format is json, apiMode behavior is assumed. if format is html, it is not.
|
// bool apiMode = false; // no longer used - if format is json, apiMode behavior is assumed. if format is html, it is not.
|
||||||
// FIXME: what if you want the data formatted server side, but still in a json envelope?
|
// FIXME: what if you want the data formatted server side, but still in a json envelope?
|
||||||
|
|
@ -815,6 +825,8 @@ struct FunctionInfo {
|
||||||
|
|
||||||
bool requireHttps;
|
bool requireHttps;
|
||||||
|
|
||||||
|
string genericContainerType = "default";
|
||||||
|
|
||||||
Document delegate(in string[string] args) createForm; /// This is used if you want a custom form - normally, on insufficient parameters, an automatic form is created. But if there's a functionName_Form method, it is used instead. FIXME: this used to work but not sure if it still does
|
Document delegate(in string[string] args) createForm; /// This is used if you want a custom form - normally, on insufficient parameters, an automatic form is created. But if there's a functionName_Form method, it is used instead. FIXME: this used to work but not sure if it still does
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1000,6 +1012,8 @@ immutable(ReflectionInfo*) prepareReflectionImpl(alias PM, alias Parent)(Parent
|
||||||
f.returnType = ReturnType!(__traits(getMember, Class, member)).stringof;
|
f.returnType = ReturnType!(__traits(getMember, Class, member)).stringof;
|
||||||
f.returnTypeIsDocument = is(ReturnType!(__traits(getMember, Class, member)) : Document);
|
f.returnTypeIsDocument = is(ReturnType!(__traits(getMember, Class, member)) : Document);
|
||||||
f.returnTypeIsElement = is(ReturnType!(__traits(getMember, Class, member)) : Element);
|
f.returnTypeIsElement = is(ReturnType!(__traits(getMember, Class, member)) : Element);
|
||||||
|
static if(hasValueAnnotation!(__traits(getMember, Class, member), GenericContainerType))
|
||||||
|
f.genericContainerType = getAnnotation!(__traits(getMember, Class, member), GenericContainerType).type;
|
||||||
|
|
||||||
f.parentObject = reflection;
|
f.parentObject = reflection;
|
||||||
|
|
||||||
|
|
@ -1573,9 +1587,9 @@ void run(Provider)(Cgi cgi, Provider instantiation, size_t pathInfoStartingPoint
|
||||||
Element e;
|
Element e;
|
||||||
auto hack = cast(ApiProvider) realObject;
|
auto hack = cast(ApiProvider) realObject;
|
||||||
if(hack !is null)
|
if(hack !is null)
|
||||||
e = hack._getGenericContainer();
|
e = hack._getGenericContainer(fun is null ? "default" : fun.genericContainerType);
|
||||||
else
|
else
|
||||||
e = instantiation._getGenericContainer();
|
e = instantiation._getGenericContainer(fun is null ? "default" : fun.genericContainerType);
|
||||||
|
|
||||||
|
|
||||||
document = e.parentDocument;
|
document = e.parentDocument;
|
||||||
|
|
@ -1881,6 +1895,7 @@ Form createAutomaticForm(Document document, string action, in Parameter[] parame
|
||||||
|
|
||||||
auto fmt = Element.make("select");
|
auto fmt = Element.make("select");
|
||||||
fmt.name = "format";
|
fmt.name = "format";
|
||||||
|
fmt.addChild("option", "Automatic").setAttribute("value", "default");
|
||||||
fmt.addChild("option", "html").setAttribute("value", "html");
|
fmt.addChild("option", "html").setAttribute("value", "html");
|
||||||
fmt.addChild("option", "table").setAttribute("value", "table");
|
fmt.addChild("option", "table").setAttribute("value", "table");
|
||||||
fmt.addChild("option", "json").setAttribute("value", "json");
|
fmt.addChild("option", "json").setAttribute("value", "json");
|
||||||
|
|
@ -2741,6 +2756,15 @@ WrapperFunction generateWrapper(alias ObjectType, string funName, alias f, R)(Re
|
||||||
|
|
||||||
// FIXME: it's awkward to call manually due to the JSONValue ref thing. Returning a string would be mega nice.
|
// FIXME: it's awkward to call manually due to the JSONValue ref thing. Returning a string would be mega nice.
|
||||||
string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue = null, string formatJsonToStringAs = null) if(is(R : ApiProvider)) {
|
string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue = null, string formatJsonToStringAs = null) if(is(R : ApiProvider)) {
|
||||||
|
|
||||||
|
if(format == "default") {
|
||||||
|
static if(is(typeof(ret) : K[N][V], size_t N, K, V)) {
|
||||||
|
format = "table";
|
||||||
|
} else {
|
||||||
|
format = "html";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string retstr;
|
string retstr;
|
||||||
if(api !is null) {
|
if(api !is null) {
|
||||||
static if(__traits(compiles, api._customFormat(ret, format))) {
|
static if(__traits(compiles, api._customFormat(ret, format))) {
|
||||||
|
|
@ -2803,8 +2827,18 @@ string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue
|
||||||
goto badType;
|
goto badType;
|
||||||
gotATable(table);
|
gotATable(table);
|
||||||
break;
|
break;
|
||||||
}
|
} else static if(is(typeof(ret) : K[N][V], size_t N, K, V)) {
|
||||||
else
|
auto table = cast(Table) Element.make("table");
|
||||||
|
table.addClass("data-display");
|
||||||
|
foreach(k, v; ret) {
|
||||||
|
auto row = table.addChild("tr");
|
||||||
|
foreach(cell; v)
|
||||||
|
table.addChild("td", to!string(cell));
|
||||||
|
}
|
||||||
|
gotATable(table);
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else
|
||||||
goto badType;
|
goto badType;
|
||||||
default:
|
default:
|
||||||
badType:
|
badType:
|
||||||
|
|
@ -3899,7 +3933,7 @@ bool checkPassword(string saltedPasswordHash, string userSuppliedPassword) {
|
||||||
/// implements the "table" format option. Works on structs and associative arrays (string[string][])
|
/// implements the "table" format option. Works on structs and associative arrays (string[string][])
|
||||||
Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) if(isArray!(T) && !isAssociativeArray!(T)) {
|
Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) if(isArray!(T) && !isAssociativeArray!(T)) {
|
||||||
auto t = cast(Table) document.createElement("table");
|
auto t = cast(Table) document.createElement("table");
|
||||||
t.border = "1";
|
t.attrs.border = "1";
|
||||||
|
|
||||||
static if(is(T == string[string][])) {
|
static if(is(T == string[string][])) {
|
||||||
string[string] allKeys;
|
string[string] allKeys;
|
||||||
|
|
@ -3929,7 +3963,7 @@ Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) i
|
||||||
|
|
||||||
odd = !odd;
|
odd = !odd;
|
||||||
}
|
}
|
||||||
} else static if(is(typeof(T[0]) == struct)) {
|
} else static if(is(typeof(arr[0]) == struct)) {
|
||||||
{
|
{
|
||||||
auto thead = t.addChild("thead");
|
auto thead = t.addChild("thead");
|
||||||
auto tr = thead.addChild("tr");
|
auto tr = thead.addChild("tr");
|
||||||
|
|
@ -3951,7 +3985,7 @@ Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) i
|
||||||
|
|
||||||
odd = !odd;
|
odd = !odd;
|
||||||
}
|
}
|
||||||
} else static assert(0);
|
} else static assert(0, T.stringof);
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue