mirror of
https://github.com/adamdruppe/arsd.git
synced 2025-04-26 13:20:05 +03:00
adding the existing stuff
This commit is contained in:
commit
391628e3d0
15 changed files with 6541 additions and 0 deletions
55
README
Normal file
55
README
Normal file
|
@ -0,0 +1,55 @@
|
|||
This is a collection of modules I find generally useful.
|
||||
|
||||
Modules are usually independent; you don't need this whole directory
|
||||
but it doesn't hurt to grab it all either.
|
||||
|
||||
Currently included are:
|
||||
|
||||
Web related
|
||||
================
|
||||
|
||||
cgi.d - base module for making webapps in D
|
||||
dom.d - an xml/html DOM based on what Javascript provides in browsers
|
||||
web.d - a fancier way to write web apps. Uses reflection to make functions
|
||||
accessible via url with minimal boilerplate in your code
|
||||
|
||||
|
||||
Database related
|
||||
================
|
||||
|
||||
database.d - main interface to databases. Includes DataObject
|
||||
mysql.d - a mysql engine for database.d (most mature of the three)
|
||||
postgres.d - a postgres engne for database.d
|
||||
sqlite.d - a sqlite engine for database.d
|
||||
|
||||
Desktop app stuff
|
||||
================
|
||||
|
||||
simpledisplay.d - gives quick and easy access to a window for drawing
|
||||
simpleaudio.d - gives minimal audio output
|
||||
|
||||
Other
|
||||
================
|
||||
|
||||
sha.d - implementations of the SHA1 and SHA256 algorithms
|
||||
png.d - provides some png read/write support
|
||||
curl.d - a small wrapper around the curl library
|
||||
csv.d - gives read support to csv files
|
||||
http.d - a lighterweight alternative to curl.d
|
||||
|
||||
|
||||
|
||||
Things I might add once I clean up the files (this can be expedited upon
|
||||
request, to an extent):
|
||||
|
||||
httpd.d - an embedded web server
|
||||
oauth.d - client/server stuff for oauth1
|
||||
html.d - a bunch of dom translation functions. Think unobstructive javascript
|
||||
on the server side
|
||||
browser.d - a very small html widget
|
||||
netman.d - handles net connections (required by httpd.d)
|
||||
imagedraft.d - (temporary name) has algorithms for images
|
||||
bmp.d - gives .bmp read/write
|
||||
dws.d - a draft of my D windowing system (also includes some Qt code)
|
||||
wav.d - reading and writing WAV files
|
||||
midi.d - reading and writing MIDI files
|
1
cgi.d
Symbolic link
1
cgi.d
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../djs/proxy/cgi.d
|
58
csv.d
Normal file
58
csv.d
Normal file
|
@ -0,0 +1,58 @@
|
|||
module arsd.csv;
|
||||
|
||||
import std.string;
|
||||
import std.array;
|
||||
|
||||
string[][] readCsv(string data) {
|
||||
data = data.replace("\r", "");
|
||||
|
||||
auto idx = data.indexOf("\n");
|
||||
//data = data[idx + 1 .. $]; // skip headers
|
||||
|
||||
string[] fields;
|
||||
string[][] records;
|
||||
|
||||
string[] current;
|
||||
|
||||
int state = 0;
|
||||
string field;
|
||||
foreach(c; data) {
|
||||
tryit: switch(state) {
|
||||
default: assert(0);
|
||||
case 0: // normal
|
||||
if(c == '"')
|
||||
state = 1;
|
||||
else if(c == ',') {
|
||||
// commit field
|
||||
current ~= field;
|
||||
field = null;
|
||||
} else if(c == '\n') {
|
||||
// commit record
|
||||
current ~= field;
|
||||
|
||||
records ~= current;
|
||||
current = null;
|
||||
field = null;
|
||||
} else
|
||||
field ~= c;
|
||||
break;
|
||||
case 1: // in quote
|
||||
if(c == '"')
|
||||
state = 2;
|
||||
else
|
||||
field ~= c;
|
||||
break;
|
||||
case 2: // is it a closing quote or an escaped one?
|
||||
if(c == '"') {
|
||||
field ~= c;
|
||||
state = 1;
|
||||
} else {
|
||||
state = 0;
|
||||
goto tryit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return records;
|
||||
}
|
188
curl.d
Normal file
188
curl.d
Normal file
|
@ -0,0 +1,188 @@
|
|||
module arsd.curl;
|
||||
|
||||
pragma(lib, "curl");
|
||||
|
||||
import std.string;
|
||||
extern(C) {
|
||||
typedef void CURL;
|
||||
typedef void curl_slist;
|
||||
|
||||
alias int CURLcode;
|
||||
alias int CURLoption;
|
||||
|
||||
enum int CURLOPT_URL = 10002;
|
||||
enum int CURLOPT_WRITEFUNCTION = 20011;
|
||||
enum int CURLOPT_WRITEDATA = 10001;
|
||||
enum int CURLOPT_POSTFIELDS = 10015;
|
||||
enum int CURLOPT_POSTFIELDSIZE = 60;
|
||||
enum int CURLOPT_POST = 47;
|
||||
enum int CURLOPT_HTTPHEADER = 10023;
|
||||
enum int CURLOPT_USERPWD = 0x00002715;
|
||||
|
||||
enum int CURLOPT_VERBOSE = 41;
|
||||
|
||||
// enum int CURLOPT_COOKIE = 22;
|
||||
enum int CURLOPT_COOKIEFILE = 10031;
|
||||
enum int CURLOPT_COOKIEJAR = 10082;
|
||||
|
||||
enum int CURLOPT_SSL_VERIFYPEER = 64;
|
||||
|
||||
enum int CURLOPT_FOLLOWLOCATION = 52;
|
||||
|
||||
CURL* curl_easy_init();
|
||||
void curl_easy_cleanup(CURL* handle);
|
||||
CURLcode curl_easy_perform(CURL* curl);
|
||||
|
||||
void curl_global_init(int flags);
|
||||
|
||||
enum int CURL_GLOBAL_ALL = 0b1111;
|
||||
|
||||
CURLcode curl_easy_setopt(CURL* handle, CURLoption option, ...);
|
||||
curl_slist* curl_slist_append(curl_slist*, const char*);
|
||||
void curl_slist_free_all(curl_slist*);
|
||||
|
||||
// size is size of item, count is how many items
|
||||
size_t write_data(void* buffer, size_t size, size_t count, void* user) {
|
||||
string* str = cast(string*) user;
|
||||
char* data = cast(char*) buffer;
|
||||
|
||||
assert(size == 1);
|
||||
|
||||
*str ~= data[0..count];
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
char* curl_easy_strerror(CURLcode errornum );
|
||||
}
|
||||
/*
|
||||
struct CurlOptions {
|
||||
string username;
|
||||
string password;
|
||||
}
|
||||
*/
|
||||
|
||||
import std.md5;
|
||||
import std.file;
|
||||
/// this automatically caches to a local file for the given time. it ignores the expires header in favor of your time to keep.
|
||||
version(linux)
|
||||
string cachedCurl(string url, int maxCacheHours) {
|
||||
string res;
|
||||
|
||||
auto cacheFile = "/tmp/arsd-curl-cache-" ~ getDigestString(url);
|
||||
|
||||
if(!std.file.exists(cacheFile) || std.file.lastModified(cacheFile) > 1000 * 60 * 60 * maxCacheHours) {
|
||||
res = curl(url);
|
||||
std.file.write(cacheFile, res);
|
||||
} else {
|
||||
res = readText(cacheFile);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
string curl(string url, string data = null, string contentType = "application/x-www-form-urlencoded") {
|
||||
return curlAuth(url, data, null, null, contentType);
|
||||
}
|
||||
|
||||
string curlCookie(string cookieFile, string url, string data = null, string contentType = "application/x-www-form-urlencoded") {
|
||||
return curlAuth(url, data, null, null, contentType, null, null, cookieFile);
|
||||
}
|
||||
|
||||
string curlAuth(string url, string data = null, string username = null, string password = null, string contentType = "application/x-www-form-urlencoded", string methodOverride = null, string[] customHeaders = null, string cookieJar = null) {
|
||||
CURL* curl = curl_easy_init();
|
||||
if(curl is null)
|
||||
throw new Exception("curl init");
|
||||
scope(exit)
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
string ret;
|
||||
|
||||
int res;
|
||||
|
||||
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
||||
|
||||
res = curl_easy_setopt(curl, CURLOPT_URL, std.string.toStringz(url));
|
||||
if(res != 0) throw new CurlException(res);
|
||||
if(username !is null) {
|
||||
res = curl_easy_setopt(curl, CURLOPT_USERPWD, std.string.toStringz(username ~ ":" ~ password));
|
||||
if(res != 0) throw new CurlException(res);
|
||||
}
|
||||
res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_data);
|
||||
if(res != 0) throw new CurlException(res);
|
||||
res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||
if(res != 0) throw new CurlException(res);
|
||||
|
||||
curl_slist* headers = null;
|
||||
//if(data !is null)
|
||||
// contentType = "";
|
||||
headers = curl_slist_append(headers, toStringz("Content-Type: " ~ contentType));
|
||||
|
||||
foreach(h; customHeaders) {
|
||||
headers = curl_slist_append(headers, toStringz(h));
|
||||
}
|
||||
scope(exit)
|
||||
curl_slist_free_all(headers);
|
||||
|
||||
if(data) {
|
||||
res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.ptr);
|
||||
if(res != 0) throw new CurlException(res);
|
||||
res = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.length);
|
||||
if(res != 0) throw new CurlException(res);
|
||||
}
|
||||
|
||||
res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
if(res != 0) throw new CurlException(res);
|
||||
|
||||
if(cookieJar !is null) {
|
||||
res = curl_easy_setopt(curl, CURLOPT_COOKIEJAR, toStringz(cookieJar));
|
||||
if(res != 0) throw new CurlException(res);
|
||||
res = curl_easy_setopt(curl, CURLOPT_COOKIEFILE, toStringz(cookieJar));
|
||||
if(res != 0) throw new CurlException(res);
|
||||
}
|
||||
|
||||
res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
if(res != 0) throw new CurlException(res);
|
||||
//res = curl_easy_setopt(curl, 81, 0); // FIXME verify host
|
||||
//if(res != 0) throw new CurlException(res);
|
||||
|
||||
res = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
if(res != 0) throw new CurlException(res);
|
||||
|
||||
if(methodOverride !is null) {
|
||||
switch(methodOverride) {
|
||||
default: assert(0);
|
||||
case "POST":
|
||||
res = curl_easy_setopt(curl, CURLOPT_POST, 1);
|
||||
break;
|
||||
case "GET":
|
||||
//curl_easy_setopt(curl, CURLOPT_POST, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto failure = curl_easy_perform(curl);
|
||||
if(failure != 0)
|
||||
throw new CurlException(failure, "\nURL" ~ url);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
class CurlException : Exception {
|
||||
this(CURLcode code, string msg = null, string file = __FILE__, int line = __LINE__) {
|
||||
string message = file ~ ":" ~ to!string(line) ~ " (" ~ to!string(code) ~ ") ";
|
||||
|
||||
auto strerror = curl_easy_strerror(code);
|
||||
|
||||
while(*strerror) {
|
||||
message ~= *strerror;
|
||||
strerror++;
|
||||
}
|
||||
|
||||
super(message ~ msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import std.conv;
|
1010
database.d
Normal file
1010
database.d
Normal file
File diff suppressed because it is too large
Load diff
1
dom.d
Symbolic link
1
dom.d
Symbolic link
|
@ -0,0 +1 @@
|
|||
/home/me/program/djs/dom.d
|
221
http.d
Normal file
221
http.d
Normal file
|
@ -0,0 +1,221 @@
|
|||
module arsd.http;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
|
||||
/**
|
||||
Gets a textual document, ignoring headers. Throws on non-text or error.
|
||||
*/
|
||||
string get(string url) {
|
||||
auto hr = httpRequest("GET", url);
|
||||
if(hr.code != 200)
|
||||
throw new Exception(format("HTTP answered %d instead of 200 on %s", hr.code, url));
|
||||
if(hr.contentType.indexOf("text/") == -1)
|
||||
throw new Exception(hr.contentType ~ " is bad content for conversion to string");
|
||||
return cast(string) hr.content;
|
||||
|
||||
}
|
||||
|
||||
static import std.uri;
|
||||
|
||||
string post(string url, string[string] args) {
|
||||
string content;
|
||||
|
||||
foreach(name, arg; args) {
|
||||
if(content.length)
|
||||
content ~= "&";
|
||||
content ~= std.uri.encode(name) ~ "=" ~ std.uri.encode(arg);
|
||||
}
|
||||
|
||||
auto hr = httpRequest("POST", url, cast(ubyte[]) content, ["Content-Type: application/x-www-form-urlencoded"]);
|
||||
if(hr.code != 200)
|
||||
throw new Exception(format("HTTP answered %d instead of 200", hr.code));
|
||||
if(hr.contentType.indexOf("text/") == -1)
|
||||
throw new Exception(hr.contentType ~ " is bad content for conversion to string");
|
||||
|
||||
return cast(string) hr.content;
|
||||
}
|
||||
|
||||
struct HttpResponse {
|
||||
int code;
|
||||
string contentType;
|
||||
string[] headers;
|
||||
ubyte[] content;
|
||||
}
|
||||
|
||||
import std.string;
|
||||
static import std.algorithm;
|
||||
import std.conv;
|
||||
|
||||
struct UriParts {
|
||||
string original;
|
||||
string method;
|
||||
string host;
|
||||
ushort port;
|
||||
string path;
|
||||
|
||||
this(string uri) {
|
||||
original = uri;
|
||||
if(uri[0..7] != "http://")
|
||||
throw new Exception("You must use an absolute, unencrypted URL.");
|
||||
|
||||
int posSlash = uri[7..$].indexOf("/");
|
||||
if(posSlash != -1)
|
||||
posSlash += 7;
|
||||
|
||||
if(posSlash == -1)
|
||||
posSlash = uri.length;
|
||||
|
||||
int posColon = uri[7..$].indexOf(":");
|
||||
if(posColon != -1)
|
||||
posColon += 7;
|
||||
|
||||
port = 80;
|
||||
|
||||
if(posColon != -1 && posColon < posSlash) {
|
||||
host = uri[7..posColon];
|
||||
port = to!ushort(uri[posColon+1..posSlash]);
|
||||
} else
|
||||
host = uri[7..posSlash];
|
||||
|
||||
path = uri[posSlash..$];
|
||||
if(path == "")
|
||||
path = "/";
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse httpRequest(string method, string uri, const(ubyte)[] content = null, string headers[] = null) {
|
||||
auto u = UriParts(uri);
|
||||
auto f = openNetwork(u.host, u.port);
|
||||
|
||||
return doHttpRequestOnFile(f, method, uri, content, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
Executes a generic http request, returning the full result. The correct formatting
|
||||
of the parameters are the caller's responsibility. Content-Length is added automatically,
|
||||
but YOU must give Content-Type!
|
||||
*/
|
||||
HttpResponse doHttpRequestOnFile(File f, string method, string uri, const(ubyte)[] content = null, string headers[] = null)
|
||||
in {
|
||||
assert(method == "POST" || method == "GET");
|
||||
}
|
||||
body {
|
||||
auto u = UriParts(uri);
|
||||
|
||||
f.writefln("%s %s HTTP/1.1", method, u.path);
|
||||
f.writefln("Host: %s", u.host);
|
||||
f.writefln("Connection: close");
|
||||
if(content !is null)
|
||||
f.writefln("Content-Length: %d", content.length);
|
||||
if(headers !is null)
|
||||
foreach(header; headers)
|
||||
f.writefln("%s", header);
|
||||
f.writefln("");
|
||||
if(content !is null)
|
||||
f.rawWrite(content);
|
||||
|
||||
|
||||
HttpResponse hr;
|
||||
cont:
|
||||
string l = f.readln();
|
||||
if(l[0..9] != "HTTP/1.1 ")
|
||||
throw new Exception("Not talking to a http server");
|
||||
|
||||
hr.code = to!int(l[9..12]); // HTTP/1.1 ### OK
|
||||
|
||||
if(hr.code == 100) { // continue
|
||||
do {
|
||||
l = readln();
|
||||
} while(l.length > 1);
|
||||
|
||||
goto cont;
|
||||
}
|
||||
|
||||
bool chunked = false;
|
||||
|
||||
foreach(line; f.byLine) {
|
||||
if(line.length <= 1)
|
||||
break;
|
||||
hr.headers ~= line.idup;
|
||||
if(line.startsWith("Content-Type: "))
|
||||
hr.contentType = line[14..$-1].idup;
|
||||
if(line.startsWith("Transfer-Encoding: chunked"))
|
||||
chunked = true;
|
||||
}
|
||||
|
||||
ubyte[] response;
|
||||
foreach(ubyte[] chunk; f.byChunk(4096)) {
|
||||
response ~= chunk;
|
||||
}
|
||||
|
||||
|
||||
if(chunked) {
|
||||
// read the hex length, stopping at a \r\n, ignoring everything between the new line but after the first non-valid hex character
|
||||
// read binary data of that length. it is our content
|
||||
// repeat until a zero sized chunk
|
||||
// then read footers as headers.
|
||||
|
||||
int state = 0;
|
||||
int size;
|
||||
int start = 0;
|
||||
for(int a = 0; a < response.length; a++) {
|
||||
switch(state) {
|
||||
case 0: // reading hex
|
||||
char c = response[a];
|
||||
if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) {
|
||||
// just keep reading
|
||||
} else {
|
||||
int power = 1;
|
||||
size = 0;
|
||||
for(int b = a-1; b >= start; b--) {
|
||||
char cc = response[b];
|
||||
if(cc >= 'a' && cc <= 'z')
|
||||
cc -= 0x20;
|
||||
int val = 0;
|
||||
if(cc >= '0' && cc <= '9')
|
||||
val = cc - '0';
|
||||
else
|
||||
val = cc - 'A';
|
||||
|
||||
size += power * val;
|
||||
power *= 16;
|
||||
}
|
||||
state++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 1: // reading until end of line
|
||||
char c = response[a];
|
||||
if(c == '\n') {
|
||||
if(size == 0)
|
||||
state = 3;
|
||||
else
|
||||
state = 2;
|
||||
}
|
||||
break;
|
||||
case 2: // reading data
|
||||
hr.content ~= response[a..a+size];
|
||||
a += size;
|
||||
a+= 2; // skipping a 13 10
|
||||
start = a;
|
||||
state = 0;
|
||||
break;
|
||||
case 3: // reading footers
|
||||
goto done; // FIXME
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
hr.content = response;
|
||||
done:
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
void main(string args[]) {
|
||||
write(post("http://arsdnet.net/bugs.php", ["test" : "hey", "again" : "what"]));
|
||||
}
|
||||
*/
|
733
mysql.d
Normal file
733
mysql.d
Normal file
|
@ -0,0 +1,733 @@
|
|||
module arsd.mysql;
|
||||
pragma(lib, "mysqlclient");
|
||||
|
||||
public import arsd.database;
|
||||
|
||||
import std.stdio;
|
||||
import std.exception;
|
||||
import std.string;
|
||||
import std.conv;
|
||||
import std.typecons;
|
||||
|
||||
class MySqlResult : ResultSet {
|
||||
private int[string] mapping;
|
||||
private MYSQL_RES* result;
|
||||
|
||||
private int itemsTotal;
|
||||
private int itemsUsed;
|
||||
|
||||
string sql;
|
||||
|
||||
this(MYSQL_RES* r, string sql) {
|
||||
result = r;
|
||||
itemsTotal = length();
|
||||
itemsUsed = 0;
|
||||
|
||||
this.sql = sql;
|
||||
|
||||
// prime it
|
||||
if(itemsTotal)
|
||||
fetchNext();
|
||||
}
|
||||
|
||||
~this() {
|
||||
if(result !is null)
|
||||
mysql_free_result(result);
|
||||
}
|
||||
|
||||
|
||||
MYSQL_FIELD[] fields() {
|
||||
int numFields = mysql_num_fields(result);
|
||||
auto fields = mysql_fetch_fields(result);
|
||||
|
||||
MYSQL_FIELD[] ret;
|
||||
for(int i = 0; i < numFields; i++) {
|
||||
ret ~= fields[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
override int length() {
|
||||
if(result is null)
|
||||
return 0;
|
||||
return cast(int) mysql_num_rows(result);
|
||||
}
|
||||
|
||||
override bool empty() {
|
||||
return itemsUsed == itemsTotal;
|
||||
}
|
||||
|
||||
override Row front() {
|
||||
return row;
|
||||
}
|
||||
|
||||
override void popFront() {
|
||||
itemsUsed++;
|
||||
if(itemsUsed < itemsTotal) {
|
||||
fetchNext();
|
||||
}
|
||||
}
|
||||
|
||||
override int getFieldIndex(string field) {
|
||||
if(mapping is null)
|
||||
makeFieldMapping();
|
||||
debug {
|
||||
if(field !in mapping)
|
||||
throw new Exception(field ~ " not in result");
|
||||
}
|
||||
return mapping[field];
|
||||
}
|
||||
|
||||
private void makeFieldMapping() {
|
||||
int numFields = mysql_num_fields(result);
|
||||
auto fields = mysql_fetch_fields(result);
|
||||
|
||||
for(int i = 0; i < numFields; i++) {
|
||||
mapping[fromCstring(fields[i].name)] = i;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchNext() {
|
||||
assert(result);
|
||||
auto r = mysql_fetch_row(result);
|
||||
uint numFields = mysql_num_fields(result);
|
||||
uint* lengths = mysql_fetch_lengths(result);
|
||||
string[] row;
|
||||
// potential FIXME: not really binary safe
|
||||
|
||||
columnIsNull.length = numFields;
|
||||
for(int a = 0; a < numFields; a++) {
|
||||
if(*(r+a) is null) {
|
||||
row ~= null;
|
||||
columnIsNull[a] = true;
|
||||
} else {
|
||||
row ~= fromCstring(*(r+a), *(lengths + a));
|
||||
columnIsNull[a] = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.row.row = row;
|
||||
this.row.resultSet = this;
|
||||
}
|
||||
|
||||
|
||||
override string[] fieldNames() {
|
||||
int numFields = mysql_num_fields(result);
|
||||
auto fields = mysql_fetch_fields(result);
|
||||
|
||||
string[] names;
|
||||
for(int i = 0; i < numFields; i++) {
|
||||
names ~= fromCstring(fields[i].name);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool[] columnIsNull;
|
||||
Row row;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class MySql : Database {
|
||||
this(string host, string user, string pass, string db) {
|
||||
mysql = enforceEx!(DatabaseException)(
|
||||
mysql_init(null),
|
||||
"Couldn't init mysql");
|
||||
enforceEx!(DatabaseException)(
|
||||
mysql_real_connect(mysql, toCstring(host), toCstring(user), toCstring(pass), toCstring(db), 0, null, 0),
|
||||
error());
|
||||
|
||||
dbname = db;
|
||||
|
||||
// we want UTF8 for everything
|
||||
|
||||
query("SET NAMES 'utf8'");
|
||||
//query("SET CHARACTER SET utf8");
|
||||
}
|
||||
|
||||
string dbname;
|
||||
|
||||
override void startTransaction() {
|
||||
query("START TRANSACTION");
|
||||
}
|
||||
|
||||
string error() {
|
||||
return fromCstring(mysql_error(mysql));
|
||||
}
|
||||
|
||||
~this() {
|
||||
mysql_close(mysql);
|
||||
}
|
||||
|
||||
int lastInsertId() {
|
||||
return cast(int) mysql_insert_id(mysql);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int insert(string table, MySqlResult result, string[string] columnsToModify, string[] columnsToSkip) {
|
||||
assert(!result.empty);
|
||||
string sql = "INSERT INTO `" ~ table ~ "` ";
|
||||
|
||||
string cols = "(";
|
||||
string vals = "(";
|
||||
bool outputted = false;
|
||||
|
||||
string[string] columns;
|
||||
auto cnames = result.fieldNames;
|
||||
foreach(i, col; result.front.toStringArray) {
|
||||
bool skipMe = false;
|
||||
foreach(skip; columnsToSkip) {
|
||||
if(cnames[i] == skip) {
|
||||
skipMe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(skipMe)
|
||||
continue;
|
||||
|
||||
if(outputted) {
|
||||
cols ~= ",";
|
||||
vals ~= ",";
|
||||
} else
|
||||
outputted = true;
|
||||
|
||||
cols ~= cnames[i];
|
||||
|
||||
if(result.columnIsNull[i] && cnames[i] !in columnsToModify)
|
||||
vals ~= "NULL";
|
||||
else {
|
||||
string v = col;
|
||||
if(cnames[i] in columnsToModify)
|
||||
v = columnsToModify[cnames[i]];
|
||||
|
||||
vals ~= "'" ~ escape(v) ~ "'";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cols ~= ")";
|
||||
vals ~= ")";
|
||||
|
||||
sql ~= cols ~ " VALUES " ~ vals;
|
||||
|
||||
query(sql);
|
||||
|
||||
result.popFront;
|
||||
|
||||
return lastInsertId;
|
||||
}
|
||||
|
||||
string escape(string str) {
|
||||
ubyte[] buffer = new ubyte[str.length * 2 + 1];
|
||||
buffer.length = mysql_real_escape_string(mysql, buffer.ptr, cast(cstring) str.ptr, str.length);
|
||||
|
||||
return cast(string) buffer;
|
||||
}
|
||||
|
||||
string escaped(T...)(string sql, T t) {
|
||||
static if(t.length > 0) {
|
||||
string fixedup;
|
||||
int pos = 0;
|
||||
|
||||
|
||||
void escAndAdd(string str, int q) {
|
||||
ubyte[] buffer = new ubyte[str.length * 2 + 1];
|
||||
buffer.length = mysql_real_escape_string(mysql, buffer.ptr, cast(cstring) str.ptr, str.length);
|
||||
|
||||
fixedup ~= sql[pos..q] ~ '\'' ~ cast(string) buffer ~ '\'';
|
||||
|
||||
}
|
||||
|
||||
foreach(a; t) {
|
||||
int q = sql[pos..$].indexOf("?");
|
||||
if(q == -1)
|
||||
break;
|
||||
q += pos;
|
||||
|
||||
static if(__traits(compiles, t is null)) {
|
||||
if(t is null)
|
||||
fixedup ~= sql[pos..q] ~ "NULL";
|
||||
else
|
||||
escAndAdd(to!string(*a), q);
|
||||
} else {
|
||||
string str = to!string(a);
|
||||
escAndAdd(str, q);
|
||||
}
|
||||
|
||||
pos = q+1;
|
||||
}
|
||||
|
||||
fixedup ~= sql[pos..$];
|
||||
|
||||
sql = fixedup;
|
||||
|
||||
//writefln("\n\nExecuting sql: %s", sql);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ResultByDataObject queryDataObject(T...)(string sql, T t) {
|
||||
// modify sql for the best data object grabbing
|
||||
sql = fixupSqlForDataObjectUse(sql);
|
||||
|
||||
auto magic = query(sql, t);
|
||||
return ResultByDataObject(cast(MySqlResult) magic, this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int affectedRows() {
|
||||
return cast(int) mysql_affected_rows(mysql);
|
||||
}
|
||||
|
||||
override ResultSet queryImpl(string sql, Variant[] args...) {
|
||||
sql = escapedVariants(this, sql, args);
|
||||
|
||||
enforceEx!(DatabaseException)(
|
||||
!mysql_query(mysql, toCstring(sql)),
|
||||
error() ~ " :::: " ~ sql);
|
||||
|
||||
return new MySqlResult(mysql_store_result(mysql), sql);
|
||||
}
|
||||
/+
|
||||
Result queryOld(T...)(string sql, T t) {
|
||||
sql = escaped(sql, t);
|
||||
|
||||
if(sql.length == 0)
|
||||
throw new DatabaseException("empty query");
|
||||
/*
|
||||
static int queryCount = 0;
|
||||
queryCount++;
|
||||
if(sql.indexOf("INSERT") != -1)
|
||||
stderr.writefln("%d: %s", queryCount, sql.replace("\n", " ").replace("\t", ""));
|
||||
*/
|
||||
|
||||
version(dryRun) {
|
||||
pragma(msg, "This is a dry run compile, no queries will be run");
|
||||
writeln(sql);
|
||||
return Result(null, null);
|
||||
}
|
||||
|
||||
enforceEx!(DatabaseException)(
|
||||
!mysql_query(mysql, toCstring(sql)),
|
||||
error() ~ " :::: " ~ sql);
|
||||
|
||||
return Result(mysql_store_result(mysql), sql);
|
||||
}
|
||||
+/
|
||||
/+
|
||||
struct ResultByAssoc {
|
||||
this(Result* r) {
|
||||
result = r;
|
||||
fields = r.fieldNames();
|
||||
}
|
||||
|
||||
ulong length() { return result.length; }
|
||||
bool empty() { return result.empty; }
|
||||
void popFront() { result.popFront(); }
|
||||
string[string] front() {
|
||||
auto r = result.front;
|
||||
string[string] ret;
|
||||
foreach(i, a; r) {
|
||||
ret[fields[i]] = a;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@disable this(this) { }
|
||||
|
||||
string[] fields;
|
||||
Result* result;
|
||||
}
|
||||
|
||||
|
||||
struct ResultByStruct(T) {
|
||||
this(Result* r) {
|
||||
result = r;
|
||||
fields = r.fieldNames();
|
||||
}
|
||||
|
||||
ulong length() { return result.length; }
|
||||
bool empty() { return result.empty; }
|
||||
void popFront() { result.popFront(); }
|
||||
T front() {
|
||||
auto r = result.front;
|
||||
string[string] ret;
|
||||
foreach(i, a; r) {
|
||||
ret[fields[i]] = a;
|
||||
}
|
||||
|
||||
T s;
|
||||
// FIXME: should use tupleOf
|
||||
foreach(member; s.tupleof) {
|
||||
if(member.stringof in ret)
|
||||
member = to!(typeof(member))(ret[member]);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@disable this(this) { }
|
||||
|
||||
string[] fields;
|
||||
Result* result;
|
||||
}
|
||||
+/
|
||||
|
||||
/+
|
||||
|
||||
|
||||
struct Result {
|
||||
private Result* heaped() {
|
||||
auto r = new Result(result, sql, false);
|
||||
|
||||
r.tupleof = this.tupleof;
|
||||
|
||||
this.itemsTotal = 0;
|
||||
this.result = null;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
this(MYSQL_RES* r, string sql, bool prime = true) {
|
||||
result = r;
|
||||
itemsTotal = length;
|
||||
itemsUsed = 0;
|
||||
this.sql = sql;
|
||||
// prime it here
|
||||
if(prime && itemsTotal)
|
||||
fetchNext();
|
||||
}
|
||||
|
||||
string sql;
|
||||
|
||||
~this() {
|
||||
if(result !is null)
|
||||
mysql_free_result(result);
|
||||
}
|
||||
|
||||
/+
|
||||
string[string][] fetchAssoc() {
|
||||
|
||||
}
|
||||
+/
|
||||
|
||||
ResultByAssoc byAssoc() {
|
||||
return ResultByAssoc(&this);
|
||||
}
|
||||
|
||||
ResultByStruct!(T) byStruct(T)() {
|
||||
return ResultByStruct!(T)(&this);
|
||||
}
|
||||
|
||||
string[] fieldNames() {
|
||||
int numFields = mysql_num_fields(result);
|
||||
auto fields = mysql_fetch_fields(result);
|
||||
|
||||
string[] names;
|
||||
for(int i = 0; i < numFields; i++) {
|
||||
names ~= fromCstring(fields[i].name);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
MYSQL_FIELD[] fields() {
|
||||
int numFields = mysql_num_fields(result);
|
||||
auto fields = mysql_fetch_fields(result);
|
||||
|
||||
MYSQL_FIELD[] ret;
|
||||
for(int i = 0; i < numFields; i++) {
|
||||
ret ~= fields[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ulong length() {
|
||||
if(result is null)
|
||||
return 0;
|
||||
return mysql_num_rows(result);
|
||||
}
|
||||
|
||||
bool empty() {
|
||||
return itemsUsed == itemsTotal;
|
||||
}
|
||||
|
||||
Row front() {
|
||||
return row;
|
||||
}
|
||||
|
||||
void popFront() {
|
||||
itemsUsed++;
|
||||
if(itemsUsed < itemsTotal) {
|
||||
fetchNext();
|
||||
}
|
||||
}
|
||||
|
||||
void fetchNext() {
|
||||
auto r = mysql_fetch_row(result);
|
||||
uint numFields = mysql_num_fields(result);
|
||||
uint* lengths = mysql_fetch_lengths(result);
|
||||
row.length = 0;
|
||||
// potential FIXME: not really binary safe
|
||||
|
||||
columnIsNull.length = numFields;
|
||||
for(int a = 0; a < numFields; a++) {
|
||||
if(*(r+a) is null) {
|
||||
row ~= null;
|
||||
columnIsNull[a] = true;
|
||||
} else {
|
||||
row ~= fromCstring(*(r+a), *(lengths + a));
|
||||
columnIsNull[a] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@disable this(this) {}
|
||||
private MYSQL_RES* result;
|
||||
|
||||
ulong itemsTotal;
|
||||
ulong itemsUsed;
|
||||
|
||||
alias string[] Row;
|
||||
|
||||
Row row;
|
||||
bool[] columnIsNull; // FIXME: should be part of the row
|
||||
}
|
||||
+/
|
||||
private:
|
||||
MYSQL* mysql;
|
||||
}
|
||||
|
||||
struct ResultByDataObject {
|
||||
this(MySqlResult r, MySql mysql) {
|
||||
result = r;
|
||||
auto fields = r.fields();
|
||||
this.mysql = mysql;
|
||||
|
||||
foreach(i, f; fields) {
|
||||
string tbl = fromCstring(f.org_table is null ? f.table : f.org_table);
|
||||
mappings[fromCstring(f.name)] = tuple(
|
||||
tbl,
|
||||
fromCstring(f.org_name is null ? f.name : f.org_name));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Tuple!(string, string)[string] mappings;
|
||||
|
||||
ulong length() { return result.length; }
|
||||
bool empty() { return result.empty; }
|
||||
void popFront() { result.popFront(); }
|
||||
DataObject front() {
|
||||
return new DataObject(mysql, result.front.toAA, mappings);
|
||||
}
|
||||
// would it be good to add a new() method? would be valid even if empty
|
||||
// it'd just fill in the ID's at random and allow you to do the rest
|
||||
|
||||
@disable this(this) { }
|
||||
|
||||
MySqlResult result;
|
||||
MySql mysql;
|
||||
}
|
||||
|
||||
extern(C) {
|
||||
typedef void MYSQL;
|
||||
typedef void MYSQL_RES;
|
||||
typedef const(ubyte)* cstring;
|
||||
|
||||
struct MYSQL_FIELD {
|
||||
cstring name; /* Name of column */
|
||||
cstring org_name; /* Original column name, if an alias */
|
||||
cstring table; /* Table of column if column was a field */
|
||||
cstring org_table; /* Org table name, if table was an alias */
|
||||
cstring db; /* Database for table */
|
||||
cstring catalog; /* Catalog for table */
|
||||
cstring def; /* Default value (set by mysql_list_fields) */
|
||||
uint length; /* Width of column (create length) */
|
||||
uint max_length; /* Max width for selected set */
|
||||
uint name_length;
|
||||
uint org_name_length;
|
||||
uint table_length;
|
||||
uint org_table_length;
|
||||
uint db_length;
|
||||
uint catalog_length;
|
||||
uint def_length;
|
||||
uint flags; /* Div flags */
|
||||
uint decimals; /* Number of decimals in field */
|
||||
uint charsetnr; /* Character set */
|
||||
uint type; /* Type of field. See mysql_com.h for types */
|
||||
// type is actually an enum btw
|
||||
}
|
||||
|
||||
typedef cstring* MYSQL_ROW;
|
||||
|
||||
cstring mysql_get_client_info();
|
||||
MYSQL* mysql_init(MYSQL*);
|
||||
uint mysql_errno(MYSQL*);
|
||||
cstring mysql_error(MYSQL*);
|
||||
|
||||
MYSQL* mysql_real_connect(MYSQL*, cstring, cstring, cstring, cstring, uint, cstring, ulong);
|
||||
|
||||
int mysql_query(MYSQL*, cstring);
|
||||
|
||||
void mysql_close(MYSQL*);
|
||||
|
||||
ulong mysql_num_rows(MYSQL_RES*);
|
||||
uint mysql_num_fields(MYSQL_RES*);
|
||||
bool mysql_eof(MYSQL_RES*);
|
||||
|
||||
ulong mysql_affected_rows(MYSQL*);
|
||||
ulong mysql_insert_id(MYSQL*);
|
||||
|
||||
MYSQL_RES* mysql_store_result(MYSQL*);
|
||||
MYSQL_RES* mysql_use_result(MYSQL*);
|
||||
|
||||
MYSQL_ROW mysql_fetch_row(MYSQL_RES *);
|
||||
uint* mysql_fetch_lengths(MYSQL_RES*);
|
||||
MYSQL_FIELD* mysql_fetch_field(MYSQL_RES*);
|
||||
MYSQL_FIELD* mysql_fetch_fields(MYSQL_RES*);
|
||||
|
||||
uint mysql_real_escape_string(MYSQL*, ubyte* to, cstring from, uint length);
|
||||
|
||||
void mysql_free_result(MYSQL_RES*);
|
||||
|
||||
}
|
||||
|
||||
import std.string;
|
||||
cstring toCstring(string c) {
|
||||
return cast(cstring) toStringz(c);
|
||||
}
|
||||
|
||||
import std.array;
|
||||
string fromCstring(cstring c, int len = -1) {
|
||||
string ret;
|
||||
if(c is null)
|
||||
return null;
|
||||
if(len == -1) {
|
||||
while(*c) {
|
||||
ret ~= cast(char) *c;
|
||||
c++;
|
||||
}
|
||||
} else
|
||||
for(int a = 0; a < len; a++)
|
||||
ret ~= cast(char) *(a+c);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
void main() {
|
||||
auto mysql = new MySql("localhost", "uname", "password", "test");
|
||||
scope(exit) delete mysql;
|
||||
|
||||
mysql.query("INSERT INTO users (id, password) VALUES (?, ?)", 10, "lol");
|
||||
|
||||
foreach(row; mysql.query("SELECT * FROM users")) {
|
||||
writefln("%s %s %s %s", row["id"], row[0], row[1], row["username"]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
struct ResultByStruct(T) {
|
||||
this(MySql.Result* r) {
|
||||
result = r;
|
||||
fields = r.fieldNames();
|
||||
}
|
||||
|
||||
ulong length() { return result.length; }
|
||||
bool empty() { return result.empty; }
|
||||
void popFront() { result.popFront(); }
|
||||
T front() {
|
||||
auto r = result.front;
|
||||
T ret;
|
||||
foreach(i, a; r) {
|
||||
ret[fields[i]] = a;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@disable this(this) { }
|
||||
|
||||
string[] fields;
|
||||
MySql.Result* result;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/+
|
||||
mysql.linq.tablename.field[key] // select field from tablename where id = key
|
||||
|
||||
mysql.link["name"].table.field[key] // select field from table where name = key
|
||||
|
||||
|
||||
auto q = mysql.prepQuery("select id from table where something");
|
||||
q.sort("name");
|
||||
q.limit(start, count);
|
||||
q.page(3, pagelength = ?);
|
||||
|
||||
q.execute(params here); // returns the same Result range as query
|
||||
+/
|
||||
|
||||
/*
|
||||
void main() {
|
||||
auto db = new MySql("localhost", "uname", "password", "test");
|
||||
foreach(item; db.queryDataObject("SELECT users.*, username
|
||||
FROM users, password_manager_accounts
|
||||
WHERE password_manager_accounts.user_id = users.id LIMIT 5")) {
|
||||
writefln("item: %s, %s", item.id, item.username);
|
||||
item.first = "new";
|
||||
item.last = "new2";
|
||||
item.username = "kill";
|
||||
//item.commitChanges();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Copyright: Adam D. Ruppe, 2009 - 2011
|
||||
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
||||
Authors: Adam D. Ruppe
|
||||
|
||||
Copyright Adam D. Ruppe 2009 - 2011.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
600
png.d
Normal file
600
png.d
Normal file
|
@ -0,0 +1,600 @@
|
|||
module arsd.png;
|
||||
|
||||
// By Adam D. Ruppe, 2009-2010, released into the public domain
|
||||
import std.stdio;
|
||||
import std.conv;
|
||||
import std.file;
|
||||
|
||||
import std.zlib;
|
||||
|
||||
public import arsd.image;
|
||||
|
||||
/**
|
||||
The return value should be casted to indexed or truecolor depending on what you need.
|
||||
|
||||
To get an image from a png file, do this:
|
||||
|
||||
auto i = cast(TrueColorImage) imageFromPng(readPng(cast(ubyte)[]) std.file.read("file.png")));
|
||||
*/
|
||||
Image imageFromPng(PNG* png) {
|
||||
PNGHeader h = getHeader(png);
|
||||
|
||||
return new IndexedImage(h.width, h.height);
|
||||
}
|
||||
|
||||
/*
|
||||
struct PNGHeader {
|
||||
uint width;
|
||||
uint height;
|
||||
ubyte depth = 8;
|
||||
ubyte type = 6; // 0 - greyscale, 2 - truecolor, 3 - indexed color, 4 - grey with alpha, 6 - true with alpha
|
||||
ubyte compressionMethod = 0; // should be zero
|
||||
ubyte filterMethod = 0; // should be zero
|
||||
ubyte interlaceMethod = 0; // bool
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
PNG* pngFromImage(IndexedImage i) {
|
||||
PNGHeader h;
|
||||
h.width = i.width;
|
||||
h.height = i.height;
|
||||
h.type = 3;
|
||||
if(i.numColors() <= 2)
|
||||
h.depth = 1;
|
||||
else if(i.numColors() <= 4)
|
||||
h.depth = 2;
|
||||
else if(i.numColors() <= 16)
|
||||
h.depth = 4;
|
||||
else if(i.numColors() <= 256)
|
||||
h.depth = 8;
|
||||
else throw new Exception("can't save this as an indexed png");
|
||||
|
||||
auto png = blankPNG(h);
|
||||
|
||||
// do palette and alpha
|
||||
// FIXME: if there is only one transparent color, set it as the special chunk for that
|
||||
|
||||
// FIXME: we'd get a smaller file size if the transparent pixels were arranged first
|
||||
Chunk palette;
|
||||
palette.type = ['P', 'L', 'T', 'E'];
|
||||
palette.size = i.palette.length * 3;
|
||||
palette.payload.length = palette.size;
|
||||
|
||||
Chunk alpha;
|
||||
if(i.hasAlpha) {
|
||||
alpha.type = ['t', 'R', 'N', 'S'];
|
||||
alpha.size = i.palette.length;
|
||||
alpha.payload.length = alpha.size;
|
||||
}
|
||||
|
||||
for(int a = 0; a < i.palette.length; a++) {
|
||||
palette.payload[a*3+0] = i.palette[a].r;
|
||||
palette.payload[a*3+1] = i.palette[a].g;
|
||||
palette.payload[a*3+2] = i.palette[a].b;
|
||||
if(i.hasAlpha)
|
||||
alpha.payload[a] = i.palette[a].a;
|
||||
}
|
||||
|
||||
palette.checksum = crc("PLTE", palette.payload);
|
||||
png.chunks ~= palette;
|
||||
if(i.hasAlpha) {
|
||||
alpha.checksum = crc("tRNS", alpha.payload);
|
||||
png.chunks ~= alpha;
|
||||
}
|
||||
|
||||
// do the datastream
|
||||
if(h.depth == 8) {
|
||||
addImageDatastreamToPng(i.data, png);
|
||||
} else {
|
||||
// gotta convert it
|
||||
ubyte[] datastream = new ubyte[i.width * i.height * 8 / h.depth]; // FIXME?
|
||||
int shift = 0;
|
||||
|
||||
switch(h.depth) {
|
||||
case 1: shift = 7; break;
|
||||
case 2: shift = 6; break;
|
||||
case 4: shift = 4; break;
|
||||
case 8: shift = 0; break;
|
||||
}
|
||||
int dsp = 0;
|
||||
int dpos = 0;
|
||||
bool justAdvanced;
|
||||
for(int y = 0; y < i.height; y++) {
|
||||
for(int x = 0; x < i.width; x++) {
|
||||
datastream[dsp] |= i.data[dpos++] << shift;
|
||||
|
||||
switch(h.depth) {
|
||||
case 1: shift-= 1; break;
|
||||
case 2: shift-= 2; break;
|
||||
case 4: shift-= 4; break;
|
||||
case 8: shift-= 8; break;
|
||||
}
|
||||
|
||||
justAdvanced = shift < 0;
|
||||
if(shift < 0) {
|
||||
dsp++;
|
||||
switch(h.depth) {
|
||||
case 1: shift = 7; break;
|
||||
case 2: shift = 6; break;
|
||||
case 4: shift = 4; break;
|
||||
case 8: shift = 0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!justAdvanced)
|
||||
dsp++;
|
||||
switch(h.depth) {
|
||||
case 1: shift = 7; break;
|
||||
case 2: shift = 6; break;
|
||||
case 4: shift = 4; break;
|
||||
case 8: shift = 0; break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
addImageDatastreamToPng(datastream, png);
|
||||
}
|
||||
|
||||
return png;
|
||||
}
|
||||
|
||||
PNG* pngFromImage(TrueColorImage i) {
|
||||
PNGHeader h;
|
||||
h.width = i.width;
|
||||
h.height = i.height;
|
||||
// FIXME: optimize it if it is greyscale or doesn't use alpha alpha
|
||||
|
||||
auto png = blankPNG(h);
|
||||
addImageDatastreamToPng(i.data, png);
|
||||
|
||||
return png;
|
||||
}
|
||||
|
||||
/*
|
||||
void main(string[] args) {
|
||||
auto a = readPng(cast(ubyte[]) read(args[1]));
|
||||
auto f = getDatastream(a);
|
||||
|
||||
foreach(i; f) {
|
||||
writef("%d ", i);
|
||||
}
|
||||
|
||||
writefln("\n\n%d", f.length);
|
||||
}
|
||||
*/
|
||||
|
||||
struct Chunk {
|
||||
uint size;
|
||||
ubyte[4] type;
|
||||
ubyte[] payload;
|
||||
uint checksum;
|
||||
}
|
||||
|
||||
struct PNG {
|
||||
uint length;
|
||||
ubyte[8] header;
|
||||
Chunk[] chunks;
|
||||
|
||||
Chunk* getChunk(string what) {
|
||||
foreach(ref c; chunks) {
|
||||
if(cast(string) c.type == what)
|
||||
return &c;
|
||||
}
|
||||
throw new Exception("no such chunk " ~ what);
|
||||
}
|
||||
|
||||
Chunk* getChunkNullable(string what) {
|
||||
foreach(ref c; chunks) {
|
||||
if(cast(string) c.type == what)
|
||||
return &c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ubyte[] writePng(PNG* p) {
|
||||
ubyte[] a;
|
||||
if(p.length)
|
||||
a.length = p.length;
|
||||
else {
|
||||
a.length = 8;
|
||||
foreach(c; p.chunks)
|
||||
a.length += c.size + 12;
|
||||
}
|
||||
uint pos;
|
||||
|
||||
a[0..8] = p.header[0..8];
|
||||
pos = 8;
|
||||
foreach(c; p.chunks) {
|
||||
a[pos++] = (c.size & 0xff000000) >> 24;
|
||||
a[pos++] = (c.size & 0x00ff0000) >> 16;
|
||||
a[pos++] = (c.size & 0x0000ff00) >> 8;
|
||||
a[pos++] = (c.size & 0x000000ff) >> 0;
|
||||
|
||||
a[pos..pos+4] = c.type[0..4];
|
||||
pos += 4;
|
||||
a[pos..pos+c.size] = c.payload[0..c.size];
|
||||
pos += c.size;
|
||||
|
||||
a[pos++] = (c.checksum & 0xff000000) >> 24;
|
||||
a[pos++] = (c.checksum & 0x00ff0000) >> 16;
|
||||
a[pos++] = (c.checksum & 0x0000ff00) >> 8;
|
||||
a[pos++] = (c.checksum & 0x000000ff) >> 0;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
PNG* readPng(ubyte[] data) {
|
||||
auto p = new PNG;
|
||||
|
||||
p.length = data.length;
|
||||
p.header[0..8] = data[0..8];
|
||||
|
||||
uint pos = 8;
|
||||
|
||||
while(pos < data.length) {
|
||||
Chunk n;
|
||||
n.size |= data[pos++] << 24;
|
||||
n.size |= data[pos++] << 16;
|
||||
n.size |= data[pos++] << 8;
|
||||
n.size |= data[pos++] << 0;
|
||||
n.type[0..4] = data[pos..pos+4];
|
||||
pos += 4;
|
||||
n.payload.length = n.size;
|
||||
n.payload[0..n.size] = data[pos..pos+n.size];
|
||||
pos += n.size;
|
||||
|
||||
n.checksum |= data[pos++] << 24;
|
||||
n.checksum |= data[pos++] << 16;
|
||||
n.checksum |= data[pos++] << 8;
|
||||
n.checksum |= data[pos++] << 0;
|
||||
|
||||
p.chunks ~= n;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
PNG* blankPNG(PNGHeader h) {
|
||||
auto p = new PNG;
|
||||
p.header = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
||||
|
||||
Chunk c;
|
||||
|
||||
c.size = 13;
|
||||
c.type = ['I', 'H', 'D', 'R'];
|
||||
|
||||
c.payload.length = 13;
|
||||
int pos = 0;
|
||||
|
||||
c.payload[pos++] = h.width >> 24;
|
||||
c.payload[pos++] = (h.width >> 16) & 0xff;
|
||||
c.payload[pos++] = (h.width >> 8) & 0xff;
|
||||
c.payload[pos++] = h.width & 0xff;
|
||||
|
||||
c.payload[pos++] = h.height >> 24;
|
||||
c.payload[pos++] = (h.height >> 16) & 0xff;
|
||||
c.payload[pos++] = (h.height >> 8) & 0xff;
|
||||
c.payload[pos++] = h.height & 0xff;
|
||||
|
||||
c.payload[pos++] = h.depth;
|
||||
c.payload[pos++] = h.type;
|
||||
c.payload[pos++] = h.compressionMethod;
|
||||
c.payload[pos++] = h.filterMethod;
|
||||
c.payload[pos++] = h.interlaceMethod;
|
||||
|
||||
|
||||
c.checksum = crc("IHDR", c.payload);
|
||||
|
||||
p.chunks ~= c;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// should NOT have any idata already.
|
||||
// FIXME: doesn't handle palettes
|
||||
void addImageDatastreamToPng(const(ubyte)[] data, PNG* png) {
|
||||
// we need to go through the lines and add the filter byte
|
||||
// then compress it into an IDAT chunk
|
||||
// then add the IEND chunk
|
||||
|
||||
PNGHeader h = getHeader(png);
|
||||
|
||||
auto bytesPerLine = h.width * 4;
|
||||
if(h.type == 3)
|
||||
bytesPerLine = h.width * 8 / h.depth;
|
||||
Chunk dat;
|
||||
dat.type = ['I', 'D', 'A', 'T'];
|
||||
int pos = 0;
|
||||
|
||||
const(ubyte)[] output;
|
||||
while(pos+bytesPerLine <= data.length) {
|
||||
output ~= 0;
|
||||
output ~= data[pos..pos+bytesPerLine];
|
||||
pos += bytesPerLine;
|
||||
}
|
||||
|
||||
auto com = cast(ubyte[]) compress(output);
|
||||
dat.size = com.length;
|
||||
dat.payload = com;
|
||||
dat.checksum = crc("IDAT", dat.payload);
|
||||
|
||||
png.chunks ~= dat;
|
||||
|
||||
Chunk c;
|
||||
|
||||
c.size = 0;
|
||||
c.type = ['I', 'E', 'N', 'D'];
|
||||
c.checksum = crc("IEND", c.payload);
|
||||
|
||||
png.chunks ~= c;
|
||||
|
||||
}
|
||||
|
||||
struct PNGHeader {
|
||||
uint width;
|
||||
uint height;
|
||||
ubyte depth = 8;
|
||||
ubyte type = 6; // 0 - greyscale, 2 - truecolor, 3 - indexed color, 4 - grey with alpha, 6 - true with alpha
|
||||
ubyte compressionMethod = 0; // should be zero
|
||||
ubyte filterMethod = 0; // should be zero
|
||||
ubyte interlaceMethod = 0; // bool
|
||||
}
|
||||
|
||||
// bKGD - palette entry for background or the RGB (16 bits each) for that. or 16 bits of grey
|
||||
|
||||
ubyte[] getDatastream(PNG* p) {
|
||||
ubyte[] compressed;
|
||||
|
||||
foreach(c; p.chunks) {
|
||||
if(cast(string) c.type != "IDAT")
|
||||
continue;
|
||||
compressed ~= c.payload;
|
||||
}
|
||||
|
||||
return cast(ubyte[]) uncompress(compressed);
|
||||
}
|
||||
|
||||
// FIXME: Assuming 8 bits per pixel
|
||||
ubyte[] getUnfilteredDatastream(PNG* p) {
|
||||
PNGHeader h = getHeader(p);
|
||||
assert(h.filterMethod == 0);
|
||||
|
||||
assert(h.type == 3); // FIXME
|
||||
assert(h.depth == 8); // FIXME
|
||||
|
||||
ubyte[] data = getDatastream(p);
|
||||
ubyte[] ufdata = new ubyte[data.length - h.height];
|
||||
|
||||
int bytesPerLine = ufdata.length / h.height;
|
||||
|
||||
int pos = 0, pos2 = 0;
|
||||
for(int a = 0; a < h.height; a++) {
|
||||
assert(data[pos2] == 0);
|
||||
ufdata[pos..pos+bytesPerLine] = data[pos2+1..pos2+bytesPerLine+1];
|
||||
pos+= bytesPerLine;
|
||||
pos2+= bytesPerLine + 1;
|
||||
}
|
||||
|
||||
return ufdata;
|
||||
}
|
||||
|
||||
ubyte[] getFlippedUnfilteredDatastream(PNG* p) {
|
||||
PNGHeader h = getHeader(p);
|
||||
assert(h.filterMethod == 0);
|
||||
|
||||
assert(h.type == 3); // FIXME
|
||||
assert(h.depth == 8 || h.depth == 4); // FIXME
|
||||
|
||||
ubyte[] data = getDatastream(p);
|
||||
ubyte[] ufdata = new ubyte[data.length - h.height];
|
||||
|
||||
int bytesPerLine = ufdata.length / h.height;
|
||||
|
||||
|
||||
int pos = ufdata.length - bytesPerLine, pos2 = 0;
|
||||
for(int a = 0; a < h.height; a++) {
|
||||
assert(data[pos2] == 0);
|
||||
ufdata[pos..pos+bytesPerLine] = data[pos2+1..pos2+bytesPerLine+1];
|
||||
pos-= bytesPerLine;
|
||||
pos2+= bytesPerLine + 1;
|
||||
}
|
||||
|
||||
return ufdata;
|
||||
}
|
||||
|
||||
ubyte getHighNybble(ubyte a) {
|
||||
return cast(ubyte)(a >> 4); // FIXME
|
||||
}
|
||||
|
||||
ubyte getLowNybble(ubyte a) {
|
||||
return a & 0x0f;
|
||||
}
|
||||
|
||||
// Takes the transparency info and returns
|
||||
ubyte[] getANDMask(PNG* p) {
|
||||
PNGHeader h = getHeader(p);
|
||||
assert(h.filterMethod == 0);
|
||||
|
||||
assert(h.type == 3); // FIXME
|
||||
assert(h.depth == 8 || h.depth == 4); // FIXME
|
||||
|
||||
assert(h.width % 8 == 0); // might actually be %2
|
||||
|
||||
ubyte[] data = getDatastream(p);
|
||||
ubyte[] ufdata = new ubyte[h.height*((((h.width+7)/8)+3)&~3)]; // gotta pad to DWORDs...
|
||||
|
||||
Color[] colors = fetchPalette(p);
|
||||
|
||||
int pos = 0, pos2 = (h.width/((h.depth == 8) ? 1 : 2)+1)*(h.height-1);
|
||||
bool bits = false;
|
||||
for(int a = 0; a < h.height; a++) {
|
||||
assert(data[pos2++] == 0);
|
||||
for(int b = 0; b < h.width; b++) {
|
||||
if(h.depth == 4) {
|
||||
ufdata[pos/8] |= ((colors[bits? getLowNybble(data[pos2]) : getHighNybble(data[pos2])].a <= 30) << (7-(pos%8)));
|
||||
} else
|
||||
ufdata[pos/8] |= ((colors[data[pos2]].a == 0) << (7-(pos%8)));
|
||||
pos++;
|
||||
if(h.depth == 4) {
|
||||
if(bits) {
|
||||
pos2++;
|
||||
}
|
||||
bits = !bits;
|
||||
} else
|
||||
pos2++;
|
||||
}
|
||||
|
||||
int pad = 0;
|
||||
for(; pad < ((pos/8) % 4); pad++) {
|
||||
ufdata[pos/8] = 0;
|
||||
pos+=8;
|
||||
}
|
||||
if(h.depth == 4)
|
||||
pos2 -= h.width + 2;
|
||||
else
|
||||
pos2-= 2*(h.width) +2;
|
||||
}
|
||||
|
||||
return ufdata;
|
||||
}
|
||||
|
||||
// Done with assumption
|
||||
|
||||
PNGHeader getHeader(PNG* p) {
|
||||
PNGHeader h;
|
||||
ubyte[] data = p.getChunk("IHDR").payload;
|
||||
|
||||
int pos = 0;
|
||||
|
||||
h.width |= data[pos++] << 24;
|
||||
h.width |= data[pos++] << 16;
|
||||
h.width |= data[pos++] << 8;
|
||||
h.width |= data[pos++] << 0;
|
||||
|
||||
h.height |= data[pos++] << 24;
|
||||
h.height |= data[pos++] << 16;
|
||||
h.height |= data[pos++] << 8;
|
||||
h.height |= data[pos++] << 0;
|
||||
|
||||
h.depth = data[pos++];
|
||||
h.type = data[pos++];
|
||||
h.compressionMethod = data[pos++];
|
||||
h.filterMethod = data[pos++];
|
||||
h.interlaceMethod = data[pos++];
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
struct Color {
|
||||
ubyte r;
|
||||
ubyte g;
|
||||
ubyte b;
|
||||
ubyte a;
|
||||
}
|
||||
|
||||
/+
|
||||
class Image {
|
||||
Color[][] trueColorData;
|
||||
ubyte[] indexData;
|
||||
|
||||
Color[] palette;
|
||||
|
||||
uint width;
|
||||
uint height;
|
||||
|
||||
this(uint w, uint h) {}
|
||||
}
|
||||
|
||||
Image fromPNG(PNG* p) {
|
||||
|
||||
}
|
||||
|
||||
PNG* toPNG(Image i) {
|
||||
|
||||
}
|
||||
+/ struct RGBQUAD {
|
||||
ubyte rgbBlue;
|
||||
ubyte rgbGreen;
|
||||
ubyte rgbRed;
|
||||
ubyte rgbReserved;
|
||||
}
|
||||
|
||||
RGBQUAD[] fetchPaletteWin32(PNG* p) {
|
||||
RGBQUAD[] colors;
|
||||
|
||||
auto palette = p.getChunk("PLTE");
|
||||
|
||||
colors.length = (palette.size) / 3;
|
||||
|
||||
for(int i = 0; i < colors.length; i++) {
|
||||
colors[i].rgbRed = palette.payload[i*3+0];
|
||||
colors[i].rgbGreen = palette.payload[i*3+1];
|
||||
colors[i].rgbBlue = palette.payload[i*3+2];
|
||||
colors[i].rgbReserved = 0;
|
||||
}
|
||||
|
||||
return colors;
|
||||
|
||||
}
|
||||
|
||||
Color[] fetchPalette(PNG* p) {
|
||||
Color[] colors;
|
||||
|
||||
auto palette = p.getChunk("PLTE");
|
||||
|
||||
Chunk* alpha = p.getChunkNullable("tRNS");
|
||||
|
||||
colors.length = palette.size / 3;
|
||||
|
||||
for(int i = 0; i < colors.length; i++) {
|
||||
colors[i].r = palette.payload[i*3+0];
|
||||
colors[i].g = palette.payload[i*3+1];
|
||||
colors[i].b = palette.payload[i*3+2];
|
||||
if(alpha !is null && i < alpha.size)
|
||||
colors[i].a = alpha.payload[i];
|
||||
else
|
||||
colors[i].a = 255;
|
||||
|
||||
//writefln("%2d: %3d %3d %3d %3d", i, colors[i].r, colors[i].g, colors[i].b, colors[i].a);
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
void replacePalette(PNG* p, Color[] colors) {
|
||||
auto palette = p.getChunk("PLTE");
|
||||
auto alpha = p.getChunk("tRNS");
|
||||
|
||||
assert(colors.length == alpha.size);
|
||||
|
||||
for(int i = 0; i < colors.length; i++) {
|
||||
palette.payload[i*3+0] = colors[i].r;
|
||||
palette.payload[i*3+1] = colors[i].g;
|
||||
palette.payload[i*3+2] = colors[i].b;
|
||||
alpha.payload[i] = colors[i].a;
|
||||
}
|
||||
|
||||
palette.checksum = crc("PLTE", palette.payload);
|
||||
alpha.checksum = crc("tRNS", alpha.payload);
|
||||
}
|
||||
|
||||
uint update_crc(in uint crc, in ubyte[] buf){
|
||||
static const uint[256] crc_table = [0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989, 3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117];
|
||||
|
||||
uint c = crc;
|
||||
|
||||
foreach(b; buf)
|
||||
c = crc_table[(c ^ b) & 0xff] ^ (c >> 8);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
// lol is just the chunk name
|
||||
uint crc(in string lol, in ubyte[] buf){
|
||||
uint c = update_crc(0xffffffffL, cast(ubyte[]) lol);
|
||||
return update_crc(c, buf) ^ 0xffffffffL;
|
||||
}
|
||||
|
218
postgres.d
Normal file
218
postgres.d
Normal file
|
@ -0,0 +1,218 @@
|
|||
module arsd.postgres;
|
||||
pragma(lib, "pq");
|
||||
|
||||
public import arsd.database;
|
||||
|
||||
import std.string;
|
||||
import std.exception;
|
||||
|
||||
// remember to CREATE DATABASE name WITH ENCODING 'utf8'
|
||||
|
||||
class PostgreSql : Database {
|
||||
// dbname = name is probably the most common connection string
|
||||
this(string connectionString) {
|
||||
conn = PQconnectdb(toStringz(connectionString));
|
||||
if(conn is null)
|
||||
throw new DatabaseException("Unable to allocate PG connection object");
|
||||
if(PQstatus(conn) != CONNECTION_OK)
|
||||
throw new DatabaseException(error());
|
||||
query("SET NAMES 'utf8'"); // D does everything with utf8
|
||||
}
|
||||
|
||||
~this() {
|
||||
PQfinish(conn);
|
||||
}
|
||||
|
||||
override void startTransaction() {
|
||||
query("START TRANSACTION");
|
||||
}
|
||||
|
||||
ResultSet queryImpl(string sql, Variant[] args...) {
|
||||
sql = escapedVariants(this, sql, args);
|
||||
|
||||
auto res = PQexec(conn, toStringz(sql));
|
||||
int ress = PQresultStatus(res);
|
||||
if(ress != PGRES_TUPLES_OK
|
||||
&& ress != PGRES_COMMAND_OK)
|
||||
throw new DatabaseException(error());
|
||||
|
||||
return new PostgresResult(res);
|
||||
}
|
||||
|
||||
string escape(string sqlData) {
|
||||
char* buffer = (new char[sqlData.length * 2 + 1]).ptr;
|
||||
int size = PQescapeString (buffer, sqlData.ptr, sqlData.length);
|
||||
|
||||
string ret = assumeUnique(buffer[0..size]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
string error() {
|
||||
return copyCString(PQerrorMessage(conn));
|
||||
}
|
||||
|
||||
private:
|
||||
PGconn* conn;
|
||||
}
|
||||
|
||||
class PostgresResult : ResultSet {
|
||||
// name for associative array to result index
|
||||
int getFieldIndex(string field) {
|
||||
if(mapping is null)
|
||||
makeFieldMapping();
|
||||
return mapping[field];
|
||||
}
|
||||
|
||||
|
||||
string[] fieldNames() {
|
||||
if(mapping is null)
|
||||
makeFieldMapping();
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
// this is a range that can offer other ranges to access it
|
||||
bool empty() {
|
||||
return position == numRows;
|
||||
}
|
||||
|
||||
Row front() {
|
||||
return row;
|
||||
}
|
||||
|
||||
void popFront() {
|
||||
position++;
|
||||
if(position < numRows)
|
||||
fetchNext;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return numRows;
|
||||
}
|
||||
|
||||
this(PGresult* res) {
|
||||
this.res = res;
|
||||
numFields = PQnfields(res);
|
||||
numRows = PQntuples(res);
|
||||
|
||||
if(numRows)
|
||||
fetchNext();
|
||||
}
|
||||
|
||||
~this() {
|
||||
PQclear(res);
|
||||
}
|
||||
|
||||
private:
|
||||
PGresult* res;
|
||||
int[string] mapping;
|
||||
string[] columnNames;
|
||||
int numFields;
|
||||
|
||||
int position;
|
||||
|
||||
int numRows;
|
||||
|
||||
Row row;
|
||||
|
||||
void fetchNext() {
|
||||
Row r;
|
||||
r.resultSet = this;
|
||||
string[] row;
|
||||
|
||||
for(int i = 0; i < numFields; i++) {
|
||||
string a;
|
||||
|
||||
if(PQgetisnull(res, position, i))
|
||||
a = null;
|
||||
else {
|
||||
a = copyCString(PQgetvalue(res, position, i), PQgetlength(res, position, i));
|
||||
|
||||
}
|
||||
row ~= a;
|
||||
}
|
||||
|
||||
r.row = row;
|
||||
this.row = r;
|
||||
}
|
||||
|
||||
void makeFieldMapping() {
|
||||
for(int i = 0; i < numFields; i++) {
|
||||
string a = copyCString(PQfname(res, i));
|
||||
|
||||
columnNames ~= a;
|
||||
mapping[a] = i;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
string copyCString(const char* c, int actualLength = -1) {
|
||||
const(char)* a = c;
|
||||
if(a is null)
|
||||
return null;
|
||||
|
||||
string ret;
|
||||
if(actualLength == -1)
|
||||
while(*a) {
|
||||
ret ~= *a;
|
||||
a++;
|
||||
}
|
||||
else {
|
||||
ret = a[0..actualLength].idup;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern(C) {
|
||||
struct PGconn;
|
||||
struct PGresult;
|
||||
|
||||
void PQfinish(PGconn*);
|
||||
PGconn* PQconnectdb(const char*);
|
||||
|
||||
int PQstatus(PGconn*); // FIXME check return value
|
||||
|
||||
const (char*) PQerrorMessage(PGconn*);
|
||||
|
||||
PGresult* PQexec(PGconn*, const char*);
|
||||
void PQclear(PGresult*);
|
||||
|
||||
int PQresultStatus(PGresult*); // FIXME check return value
|
||||
|
||||
int PQnfields(PGresult*); // number of fields in a result
|
||||
const(char*) PQfname(PGresult*, int); // name of field
|
||||
|
||||
int PQntuples(PGresult*); // number of rows in result
|
||||
const(char*) PQgetvalue(PGresult*, int row, int column);
|
||||
|
||||
size_t PQescapeString (char *to, const char *from, size_t length);
|
||||
|
||||
enum int CONNECTION_OK = 0;
|
||||
enum int PGRES_COMMAND_OK = 1;
|
||||
enum int PGRES_TUPLES_OK = 2;
|
||||
|
||||
int PQgetlength(const PGresult *res,
|
||||
int row_number,
|
||||
int column_number);
|
||||
int PQgetisnull(const PGresult *res,
|
||||
int row_number,
|
||||
int column_number);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
import std.stdio;
|
||||
void main() {
|
||||
auto db = new PostgreSql("dbname = test");
|
||||
|
||||
db.query("INSERT INTO users (id, name) values (?, ?)", 30, "hello mang");
|
||||
|
||||
foreach(line; db.query("SELECT * FROM users")) {
|
||||
writeln(line[0], line["name"]);
|
||||
}
|
||||
}
|
||||
*/
|
366
sha.d
Normal file
366
sha.d
Normal file
|
@ -0,0 +1,366 @@
|
|||
module arsd.sha;
|
||||
|
||||
/*
|
||||
By Adam D. Ruppe, 26 Nov 2009
|
||||
I release this file into the public domain
|
||||
*/
|
||||
import std.stdio;
|
||||
|
||||
immutable(ubyte)[/*20*/] SHA1(T)(T data) if(isInputRange!(T)) /*const(ubyte)[] data)*/ {
|
||||
uint[5] h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
|
||||
|
||||
SHARange!(T) range;
|
||||
static if(is(data == SHARange))
|
||||
range = data;
|
||||
else {
|
||||
range.r = data;
|
||||
}
|
||||
/*
|
||||
ubyte[] message = data.dup;
|
||||
message ~= 0b1000_0000;
|
||||
while(((message.length+8) * 8) % 512)
|
||||
message ~= 0;
|
||||
|
||||
ulong originalLength = cast(ulong) data.length * 8;
|
||||
|
||||
for(int a = 7; a >= 0; a--)
|
||||
message ~= (originalLength >> (a*8)) & 0xff; // to big-endian
|
||||
|
||||
assert(((message.length * 8) % 512) == 0);
|
||||
|
||||
uint pos = 0;
|
||||
while(pos < message.length) {
|
||||
*/
|
||||
while(!range.empty) {
|
||||
uint[80] words;
|
||||
|
||||
for(int a = 0; a < 16; a++) {
|
||||
for(int b = 3; b >= 0; b--) {
|
||||
words[a] |= cast(uint)(range.front()) << (b*8);
|
||||
range.popFront;
|
||||
// words[a] |= cast(uint)(message[pos]) << (b*8);
|
||||
// pos++;
|
||||
}
|
||||
}
|
||||
|
||||
for(int a = 16; a < 80; a++) {
|
||||
uint t = words[a-3];
|
||||
t ^= words[a-8];
|
||||
t ^= words[a-14];
|
||||
t ^= words[a-16];
|
||||
asm { rol t, 1; }
|
||||
words[a] = t;
|
||||
}
|
||||
|
||||
uint a = h[0];
|
||||
uint b = h[1];
|
||||
uint c = h[2];
|
||||
uint d = h[3];
|
||||
uint e = h[4];
|
||||
|
||||
for(int i = 0; i < 80; i++) {
|
||||
uint f, k;
|
||||
if(i >= 0 && i < 20) {
|
||||
f = (b & c) | ((~b) & d);
|
||||
k = 0x5A827999;
|
||||
} else
|
||||
if(i >= 20 && i < 40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 0x6ED9EBA1;
|
||||
} else
|
||||
if(i >= 40 && i < 60) {
|
||||
f = (b & c) | (b & d) | (c & d);
|
||||
k = 0x8F1BBCDC;
|
||||
} else
|
||||
if(i >= 60 && i < 80) {
|
||||
f = b ^ c ^ d;
|
||||
k = 0xCA62C1D6;
|
||||
} else assert(0);
|
||||
|
||||
uint temp;
|
||||
asm {
|
||||
mov EAX, a;
|
||||
rol EAX, 5;
|
||||
add EAX, f;
|
||||
add EAX, e;
|
||||
add EAX, k;
|
||||
mov temp, EAX;
|
||||
}
|
||||
temp += words[i];
|
||||
e = d;
|
||||
d = c;
|
||||
asm {
|
||||
mov EAX, b;
|
||||
rol EAX, 30;
|
||||
mov c, EAX;
|
||||
}
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
h[0] += a;
|
||||
h[1] += b;
|
||||
h[2] += c;
|
||||
h[3] += d;
|
||||
h[4] += e;
|
||||
}
|
||||
|
||||
|
||||
ubyte[] hash;
|
||||
for(int j = 0; j < 5; j++)
|
||||
for(int i = 3; i >= 0; i--) {
|
||||
hash ~= cast(ubyte)(h[j] >> (i*8))&0xff;
|
||||
}
|
||||
|
||||
return hash.idup;
|
||||
}
|
||||
|
||||
import std.range;
|
||||
|
||||
// This does the preprocessing of input data, fetching one byte at a time of the data until it is empty, then the padding and length at the end
|
||||
template SHARange(T) if(isInputRange!(T)) {
|
||||
struct SHARange {
|
||||
T r;
|
||||
|
||||
bool empty() {
|
||||
return state == 5;
|
||||
}
|
||||
|
||||
void popFront() {
|
||||
static int lol = 0;
|
||||
if(state == 0) {
|
||||
r.popFront;
|
||||
/*
|
||||
static if(__traits(compiles, r.front.length))
|
||||
length += r.front.length;
|
||||
else
|
||||
length += r.front().sizeof;
|
||||
*/
|
||||
length++; // FIXME
|
||||
|
||||
if(r.empty) {
|
||||
state = 1;
|
||||
position = 2;
|
||||
current = 0x80;
|
||||
}
|
||||
} else {
|
||||
if(state == 1) {
|
||||
current = 0x0;
|
||||
state = 2;
|
||||
position++;
|
||||
} else if( state == 2) {
|
||||
if(!(((position + length + 8) * 8) % 512)) {
|
||||
state = 3;
|
||||
position = 7;
|
||||
length *= 8;
|
||||
} else
|
||||
position++;
|
||||
} else if (state == 3) {
|
||||
current = (length >> (position*8)) & 0xff;
|
||||
if(position == 0)
|
||||
state = 4;
|
||||
else
|
||||
position--;
|
||||
} else if (state == 4) {
|
||||
current = 0xff;
|
||||
state = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ubyte front() {
|
||||
if(state == 0) {
|
||||
return cast(ubyte) r.front();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
ubyte current;
|
||||
uint position;
|
||||
ulong length;
|
||||
int state = 0; // reading range, reading appended bit, reading padding, reading length, done
|
||||
}
|
||||
}
|
||||
|
||||
immutable(ubyte)[] SHA256(T)(T data) if ( isInputRange!(T)) {
|
||||
uint[8] h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
||||
immutable(uint[64]) k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
|
||||
|
||||
SHARange!(T) range;
|
||||
static if(is(data == SHARange))
|
||||
range = data;
|
||||
else {
|
||||
range.r = data;
|
||||
}
|
||||
/*
|
||||
ubyte[] message = cast(ubyte[]) data.dup;
|
||||
message ~= 0b1000_0000;
|
||||
while(((message.length+8) * 8) % 512)
|
||||
message ~= 0;
|
||||
|
||||
ulong originalLength = cast(ulong) data.length * 8;
|
||||
|
||||
for(int a = 7; a >= 0; a--)
|
||||
message ~= (originalLength >> (a*8)) & 0xff; // to big-endian
|
||||
|
||||
assert(((message.length * 8) % 512) == 0);
|
||||
*/
|
||||
// uint pos = 0;
|
||||
while(!range.empty) {
|
||||
// while(pos < message.length) {
|
||||
uint[64] words;
|
||||
|
||||
for(int a = 0; a < 16; a++) {
|
||||
for(int b = 3; b >= 0; b--) {
|
||||
words[a] |= cast(uint)(range.front()) << (b*8);
|
||||
//words[a] |= cast(uint)(message[pos]) << (b*8);
|
||||
range.popFront;
|
||||
// pos++;
|
||||
}
|
||||
}
|
||||
|
||||
for(int a = 16; a < 64; a++) {
|
||||
uint t1 = words[a-15];
|
||||
asm {
|
||||
mov EAX, t1;
|
||||
mov EBX, EAX;
|
||||
mov ECX, EAX;
|
||||
ror EAX, 7;
|
||||
ror EBX, 18;
|
||||
shr ECX, 3;
|
||||
xor EAX, EBX;
|
||||
xor EAX, ECX;
|
||||
mov t1, EAX;
|
||||
}
|
||||
uint t2 = words[a-2];
|
||||
asm {
|
||||
mov EAX, t2;
|
||||
mov EBX, EAX;
|
||||
mov ECX, EAX;
|
||||
ror EAX, 17;
|
||||
ror EBX, 19;
|
||||
shr ECX, 10;
|
||||
xor EAX, EBX;
|
||||
xor EAX, ECX;
|
||||
mov t2, EAX;
|
||||
}
|
||||
|
||||
words[a] = words[a-16] + t1 + words[a-7] + t2;
|
||||
}
|
||||
|
||||
uint A = h[0];
|
||||
uint B = h[1];
|
||||
uint C = h[2];
|
||||
uint D = h[3];
|
||||
uint E = h[4];
|
||||
uint F = h[5];
|
||||
uint G = h[6];
|
||||
uint H = h[7];
|
||||
|
||||
for(int i = 0; i < 64; i++) {
|
||||
uint s0;
|
||||
asm {
|
||||
mov EAX, A;
|
||||
mov EBX, EAX;
|
||||
mov ECX, EAX;
|
||||
ror EAX, 2;
|
||||
ror EBX, 13;
|
||||
ror ECX, 22;
|
||||
xor EAX, EBX;
|
||||
xor EAX, ECX;
|
||||
mov s0, EAX;
|
||||
}
|
||||
uint maj = (A & B) ^ (A & C) ^ (B & C);
|
||||
uint t2 = s0 + maj;
|
||||
uint s1;
|
||||
asm {
|
||||
mov EAX, E;
|
||||
mov EBX, EAX;
|
||||
mov ECX, EAX;
|
||||
ror EAX, 6;
|
||||
ror EBX, 11;
|
||||
ror ECX, 25;
|
||||
xor EAX, EBX;
|
||||
xor EAX, ECX;
|
||||
mov s1, EAX;
|
||||
}
|
||||
uint ch = (E & F) ^ ((~E) & G);
|
||||
uint t1 = H + s1 + ch + k[i] + words[i];
|
||||
|
||||
H = G;
|
||||
G = F;
|
||||
F = E;
|
||||
E = D + t1;
|
||||
D = C;
|
||||
C = B;
|
||||
B = A;
|
||||
A = t1 + t2;
|
||||
}
|
||||
|
||||
h[0] += A;
|
||||
h[1] += B;
|
||||
h[2] += C;
|
||||
h[3] += D;
|
||||
h[4] += E;
|
||||
h[5] += F;
|
||||
h[6] += G;
|
||||
h[7] += H;
|
||||
}
|
||||
|
||||
ubyte[] hash;
|
||||
for(int j = 0; j < 8; j++)
|
||||
for(int i = 3; i >= 0; i--) {
|
||||
hash ~= cast(ubyte)(h[j] >> (i*8))&0xff;
|
||||
}
|
||||
|
||||
return hash.idup;
|
||||
}
|
||||
|
||||
import std.exception;
|
||||
|
||||
string hashToString(const(ubyte)[] hash) {
|
||||
char[] s;
|
||||
|
||||
s.length = hash.length * 2;
|
||||
|
||||
char toHex(int a) {
|
||||
if(a < 10)
|
||||
return cast(char) (a + '0');
|
||||
else
|
||||
return cast(char) (a + 'a' - 10);
|
||||
}
|
||||
|
||||
for(int a = 0; a < hash.length; a++) {
|
||||
s[a*2] = toHex(hash[a] >> 4);
|
||||
s[a*2+1] = toHex(hash[a] & 0x0f);
|
||||
}
|
||||
|
||||
return assumeUnique(s);
|
||||
}
|
||||
/*
|
||||
string tee(string t) {
|
||||
writefln("%s", t);
|
||||
return t;
|
||||
}
|
||||
*/
|
||||
unittest {
|
||||
assert(hashToString(SHA1("abc")) == "a9993e364706816aba3e25717850c26c9cd0d89d");
|
||||
assert(hashToString(SHA1("sdfj983yr2ih")) == "335f1f5a4af4aa2c8e93b88d69dda2c22baeb94d");
|
||||
assert(hashToString(SHA1("$%&^54ylkufg09fd7f09sa7udsiouhcx987yw98etf7yew98yfds987f632<F7>uw90ruds09fudsf09dsuhfoidschyds98fydovipsdaidsd9fsa GA UIA duisguifgsuifgusaufisgfuisafguisagasuidgsaufsauifhuisahfuisafaoisahasiosafhffdasasdisayhfdoisayf8saiuhgduifyds8fiydsufisafoisayf8sayfd98wqyr98wqy98sayd98sayd098sayd09sayd98sayd98saicxyhckxnvjbpovc pousa09cusa 09csau csa9 dusa90d usa9d0sau dsa90 as09posufpodsufodspufdspofuds 9tu sapfusaa daosjdoisajdsapoihdsaiodyhsaioyfg d98ytewq89rysa 98yc98sdxych sa89ydsa89dy sa98ydas98c ysx9v8y cxv89ysd f8ysa89f ysa89fd sg8yhds9g8 rfjcxhvslkhdaiosy09wq7r987t98e7ys98aIYOIYOIY)(*YE (*WY *A(YSA* HDUIHDUIAYT&*ATDAUID AUI DUIAT DUIAG saoidusaoid ysqoid yhsaduiayh UIZYzuI YUIYEDSA UIDYUIADYISA YTDGS UITGUID")) == "e38a1220eaf8103d6176df2e0dd0a933e2f52001");
|
||||
|
||||
assert(hashToString(SHA256("abc")) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
||||
assert(hashToString(SHA256("$%&^54ylkufg09fd7f09sa7udsiouhcx987yw98etf7yew98yfds987f632<F7>uw90ruds09fudsf09dsuhfoidschyds98fydovipsdaidsd9fsa GA UIA duisguifgsuifgusaufisgfuisafguisagasuidgsaufsauifhuisahfuisafaoisahasiosafhffdasasdisayhfdoisayf8saiuhgduifyds8fiydsufisafoisayf8sayfd98wqyr98wqy98sayd98sayd098sayd09sayd98sayd98saicxyhckxnvjbpovc pousa09cusa 09csau csa9 dusa90d usa9d0sau dsa90 as09posufpodsufodspufdspofuds 9tu sapfusaa daosjdoisajdsapoihdsaiodyhsaioyfg d98ytewq89rysa 98yc98sdxych sa89ydsa89dy sa98ydas98c ysx9v8y cxv89ysd f8ysa89f ysa89fd sg8yhds9g8 rfjcxhvslkhdaiosy09wq7r987t98e7ys98aIYOIYOIY)(*YE (*WY *A(YSA* HDUIHDUIAYT&*ATDAUID AUI DUIAT DUIAG saoidusaoid ysqoid yhsaduiayh UIZYzuI YUIYEDSA UIDYUIADYISA YTDGS UITGUID")) == "64ff79c67ad5ddf9ba5b2d83e07a6937ef9a5b4eb39c54fe1e913e21aad0e95c");
|
||||
}
|
||||
/*
|
||||
void main() {
|
||||
auto hash = SHA256(InputByChar(stdin));
|
||||
writefln("%s", hashToString(hash));
|
||||
}
|
||||
*/
|
1
simpleaudio.d
Symbolic link
1
simpleaudio.d
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../dimage/simpleaudio.d
|
1
simpledisplay.d
Symbolic link
1
simpledisplay.d
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../dimage/simpledisplay.d
|
740
sqlite.d
Normal file
740
sqlite.d
Normal file
|
@ -0,0 +1,740 @@
|
|||
/*
|
||||
Compile with version=sqlite_extended_metadata_available
|
||||
if your sqlite is compiled with the
|
||||
SQLITE_ENABLE_COLUMN_METADATA C-preprocessor symbol.
|
||||
|
||||
If you enable that, you get the ability to use the
|
||||
queryDataObject() function with sqlite. (You can still
|
||||
use DataObjects, but you'll have to set up the mappings
|
||||
manually without the extended metadata.)
|
||||
*/
|
||||
|
||||
module arsd.sqlite;
|
||||
pragma(lib, "sqlite3");
|
||||
version(linux)
|
||||
pragma(lib, "dl"); // apparently sqlite3 depends on this
|
||||
public import arsd.database;
|
||||
|
||||
import std.exception;
|
||||
|
||||
import std.string;
|
||||
|
||||
import std.c.stdlib;
|
||||
import core.exception;
|
||||
import core.memory;
|
||||
import std.file;
|
||||
import std.conv;
|
||||
/*
|
||||
NOTE:
|
||||
|
||||
This only works correctly on INSERTs if the user can grow the
|
||||
database file! This means he must have permission to write to
|
||||
both the file and the directory it is in.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
The Database interface provides a consistent and safe way to access sql RDBMSs.
|
||||
|
||||
Why are all the classes scope? To ensure the database connection is closed when you are done with it.
|
||||
The destructor cleans everything up.
|
||||
|
||||
(maybe including rolling back a transaction if one is going and it errors.... maybe, or that could bne
|
||||
scope(exit))
|
||||
*/
|
||||
|
||||
Sqlite openDBAndCreateIfNotPresent(string filename, string sql, void delegate(Sqlite db) initalize = null){
|
||||
if(exists(filename))
|
||||
return new Sqlite(filename);
|
||||
else {
|
||||
auto db = new Sqlite(filename);
|
||||
db.exec(sql);
|
||||
if(initalize !is null)
|
||||
initalize(db);
|
||||
return db;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
import std.stdio;
|
||||
void main() {
|
||||
Database db = new Sqlite("test.sqlite.db");
|
||||
|
||||
db.query("CREATE TABLE users (id integer, name text)");
|
||||
|
||||
db.query("INSERT INTO users values (?, ?)", 1, "hello");
|
||||
|
||||
foreach(line; db.query("SELECT * FROM users")) {
|
||||
writefln("%s %s", line[0], line["name"]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class Sqlite : Database {
|
||||
public:
|
||||
this(string filename, int flags = SQLITE_OPEN_READWRITE) {
|
||||
/+
|
||||
int error = sqlite3_open_v2(toStringz(filename), &db, flags, null);
|
||||
if(error == SQLITE_CANTOPEN)
|
||||
throw new DatabaseException("omg cant open");
|
||||
if(error != SQLITE_OK)
|
||||
throw new DatabaseException("db open " ~ error());
|
||||
+/
|
||||
int error = sqlite3_open(toStringz(filename), &db);
|
||||
if(error != SQLITE_OK)
|
||||
throw new DatabaseException(this.error());
|
||||
}
|
||||
|
||||
~this(){
|
||||
if(sqlite3_close(db) != SQLITE_OK)
|
||||
throw new DatabaseException(error());
|
||||
}
|
||||
|
||||
// my extension for easier editing
|
||||
version(sqlite_extended_metadata_available) {
|
||||
ResultByDataObject queryDataObject(T...)(string sql, T t) {
|
||||
// modify sql for the best data object grabbing
|
||||
sql = fixupSqlForDataObjectUse(sql);
|
||||
|
||||
auto s = Statement(this, sql);
|
||||
foreach(i, arg; t) {
|
||||
s.bind(i + 1, arg);
|
||||
}
|
||||
|
||||
auto magic = s.execute(true); // fetch extended metadata
|
||||
|
||||
return ResultByDataObject(cast(SqliteResult) magic, magic.extendedMetadata, this);
|
||||
}
|
||||
}
|
||||
|
||||
override void startTransaction() {
|
||||
query("BEGIN TRANSACTION");
|
||||
}
|
||||
|
||||
override ResultSet queryImpl(string sql, Variant[] args...) {
|
||||
auto s = Statement(this, sql);
|
||||
foreach(i, arg; args) {
|
||||
s.bind(i + 1, arg);
|
||||
}
|
||||
|
||||
return s.execute();
|
||||
}
|
||||
|
||||
override string escape(string sql) {
|
||||
if(sql is null)
|
||||
return null;
|
||||
char* got = sqlite3_mprintf("%q", toStringz(sql)); // FIXME: might have to be %Q, need to check this, but I think the other impls do the same as %q
|
||||
auto orig = got;
|
||||
string esc;
|
||||
while(*got) {
|
||||
esc ~= (*got);
|
||||
got++;
|
||||
}
|
||||
|
||||
sqlite3_free(orig);
|
||||
|
||||
return esc;
|
||||
}
|
||||
|
||||
string error(){
|
||||
char* mesg = sqlite3_errmsg(db);
|
||||
char[] m;
|
||||
int a = std.c.string.strlen(mesg);
|
||||
m.length = a;
|
||||
for(int v = 0; v < a; v++)
|
||||
m[v] = mesg[v];
|
||||
|
||||
return assumeUnique(m);
|
||||
}
|
||||
|
||||
int affectedRows(){
|
||||
return sqlite3_changes(db);
|
||||
}
|
||||
|
||||
int lastInsertId(){
|
||||
return cast(int) sqlite3_last_insert_rowid(db);
|
||||
}
|
||||
|
||||
|
||||
int exec(string sql, void delegate (char[][char[]]) onEach = null) {
|
||||
char* mesg;
|
||||
if(sqlite3_exec(db, toStringz(sql), &callback, &onEach, &mesg) != SQLITE_OK) {
|
||||
char[] m;
|
||||
int a = std.c.string.strlen(mesg);
|
||||
m.length = a;
|
||||
for(int v = 0; v < a; v++)
|
||||
m[v] = mesg[v];
|
||||
|
||||
sqlite3_free(mesg);
|
||||
throw new DatabaseException("exec " ~ m.idup);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
Statement prepare(string sql){
|
||||
sqlite3_stmt * s;
|
||||
if(sqlite3_prepare_v2(db, toStringz(sql), cast(int) sql.length, &s, null) != SQLITE_OK)
|
||||
throw new DatabaseException("prepare " ~ error());
|
||||
|
||||
Statement a = new Statement(s);
|
||||
|
||||
return a;
|
||||
}
|
||||
*/
|
||||
private:
|
||||
sqlite3* db;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SqliteResult : ResultSet {
|
||||
int getFieldIndex(string field) {
|
||||
foreach(i, n; columnNames)
|
||||
if(n == field)
|
||||
return i;
|
||||
throw new Exception("no such field " ~ field);
|
||||
}
|
||||
|
||||
string[] fieldNames() {
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
// this is a range that can offer other ranges to access it
|
||||
bool empty() {
|
||||
return position == rows.length;
|
||||
}
|
||||
|
||||
Row front() {
|
||||
Row r;
|
||||
|
||||
r.resultSet = this;
|
||||
if(rows.length <= position)
|
||||
throw new Exception("Result is empty");
|
||||
foreach(c; rows[position]) {
|
||||
r.row ~= c.coerce!(string);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void popFront() {
|
||||
position++;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return rows.length;
|
||||
}
|
||||
|
||||
this(Variant[][] rows, char[][] columnNames) {
|
||||
this.rows = rows;
|
||||
foreach(c; columnNames)
|
||||
this.columnNames ~= c.idup;
|
||||
}
|
||||
|
||||
private:
|
||||
string[] columnNames;
|
||||
Variant[][] rows;
|
||||
int position = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct Statement {
|
||||
private this(Sqlite db, sqlite3_stmt * S) {
|
||||
this.db = db;
|
||||
s = S;
|
||||
finalized = false;
|
||||
}
|
||||
|
||||
Sqlite db;
|
||||
|
||||
this(Sqlite db, string sql) {
|
||||
this.db = db;
|
||||
if(sqlite3_prepare_v2(db.db, toStringz(sql), cast(int) sql.length, &s, null) != SQLITE_OK)
|
||||
throw new DatabaseException(db.error());
|
||||
}
|
||||
|
||||
version(sqlite_extended_metadata_available)
|
||||
Tuple!(string, string)[string] extendedMetadata;
|
||||
|
||||
ResultSet execute(bool fetchExtendedMetadata = false) {
|
||||
bool first = true;
|
||||
int count;
|
||||
int numRows = 0;
|
||||
int r = 0;
|
||||
// FIXME: doesn't handle busy database
|
||||
while( SQLITE_ROW == sqlite3_step(s) ){
|
||||
numRows++;
|
||||
if(numRows >= rows.length)
|
||||
rows.length = rows.length + 8;
|
||||
|
||||
if(first){
|
||||
count = sqlite3_column_count(s);
|
||||
|
||||
columnNames.length = count;
|
||||
for(int a = 0; a < count; a++){
|
||||
char* str = sqlite3_column_name(s, a);
|
||||
int l = std.c.string.strlen(str);
|
||||
columnNames[a].length = l;
|
||||
for(int b = 0; b < l; b++)
|
||||
columnNames[a][b] = str[b];
|
||||
|
||||
version(sqlite_extended_metadata_available) {
|
||||
if(fetchExtendedMetadata) {
|
||||
string origtbl;
|
||||
string origcol;
|
||||
|
||||
const(char)* rofl;
|
||||
|
||||
rofl = sqlite3_column_table_name(s, a);
|
||||
if(rofl is null)
|
||||
throw new Exception("null table name pointer");
|
||||
while(*rofl) {
|
||||
origtbl ~= *rofl;
|
||||
rofl++;
|
||||
}
|
||||
rofl = sqlite3_column_origin_name(s, a);
|
||||
if(rofl is null)
|
||||
throw new Exception("null colum name pointer");
|
||||
while(*rofl) {
|
||||
origcol ~= *rofl;
|
||||
rofl++;
|
||||
}
|
||||
extendedMetadata[columnNames[a].idup] = tuple(origtbl, origcol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
|
||||
rows[r].length = count;
|
||||
|
||||
for(int a = 0; a < count; a++){
|
||||
Variant v;
|
||||
switch(sqlite3_column_type(s, a)){
|
||||
case SQLITE_INTEGER:
|
||||
v = sqlite3_column_int(s, a);
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
v = sqlite3_column_double(s, a);
|
||||
break;
|
||||
case SQLITE3_TEXT:
|
||||
char* str = sqlite3_column_text(s, a);
|
||||
char[] st;
|
||||
|
||||
int l = std.c.string.strlen(str);
|
||||
st.length = l;
|
||||
for(int aa = 0; aa < l; aa++)
|
||||
st[aa] = str[aa];
|
||||
|
||||
v = assumeUnique(st);
|
||||
break;
|
||||
case SQLITE_BLOB:
|
||||
byte* str = cast(byte*) sqlite3_column_blob(s, a);
|
||||
byte[] st;
|
||||
|
||||
int l = sqlite3_column_bytes(s, a);
|
||||
st.length = l;
|
||||
for(int aa = 0; aa < l; aa++)
|
||||
st[aa] = str[aa];
|
||||
|
||||
v = assumeUnique(st);
|
||||
|
||||
break;
|
||||
case SQLITE_NULL:
|
||||
v = null;
|
||||
break;
|
||||
}
|
||||
|
||||
rows[r][a] = v;
|
||||
}
|
||||
|
||||
r++;
|
||||
}
|
||||
|
||||
rows.length = numRows;
|
||||
length = numRows;
|
||||
position = 0;
|
||||
executed = true;
|
||||
reset();
|
||||
|
||||
return new SqliteResult(rows.dup, columnNames);
|
||||
}
|
||||
|
||||
/*
|
||||
template extract(A, T, R...){
|
||||
void extract(A args, out T t, out R r){
|
||||
if(r.length + 1 != args.length)
|
||||
throw new DatabaseException("wrong places");
|
||||
args[0].to(t);
|
||||
static if(r.length)
|
||||
extract(args[1..$], r);
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*
|
||||
bool next(T, R...)(out T t, out R r){
|
||||
if(position == length)
|
||||
return false;
|
||||
|
||||
extract(rows[position], t, r);
|
||||
|
||||
position++;
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
bool step(out Variant[] row){
|
||||
assert(executed);
|
||||
if(position == length)
|
||||
return false;
|
||||
|
||||
row = rows[position];
|
||||
position++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool step(out Variant[char[]] row){
|
||||
assert(executed);
|
||||
if(position == length)
|
||||
return false;
|
||||
|
||||
for(int a = 0; a < length; a++)
|
||||
row[columnNames[a].idup] = rows[position][a];
|
||||
|
||||
position++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset(){
|
||||
if(sqlite3_reset(s) != SQLITE_OK)
|
||||
throw new DatabaseException("reset " ~ db.error());
|
||||
}
|
||||
|
||||
void resetBindings(){
|
||||
sqlite3_clear_bindings(s);
|
||||
}
|
||||
|
||||
void resetAll(){
|
||||
reset;
|
||||
resetBindings;
|
||||
executed = false;
|
||||
}
|
||||
|
||||
int bindNameLookUp(const char[] name){
|
||||
int a = sqlite3_bind_parameter_index(s, toStringz(name));
|
||||
if(a == 0)
|
||||
throw new DatabaseException("bind name lookup failed " ~ db.error());
|
||||
return a;
|
||||
}
|
||||
|
||||
bool next(T, R...)(out T t, out R r){
|
||||
assert(executed);
|
||||
if(position == length)
|
||||
return false;
|
||||
|
||||
extract(rows[position], t, r);
|
||||
|
||||
position++;
|
||||
return true;
|
||||
}
|
||||
|
||||
template bindAll(T, R...){
|
||||
void bindAll(T what, R more){
|
||||
bindAllHelper(1, what, more);
|
||||
}
|
||||
}
|
||||
|
||||
template exec(T, R...){
|
||||
void exec(T what, R more){
|
||||
bindAllHelper(1, what, more);
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
void bindAllHelper(A, T, R...)(A where, T what, R more){
|
||||
bind(where, what);
|
||||
static if(more.length)
|
||||
bindAllHelper(where + 1, more);
|
||||
}
|
||||
|
||||
//void bind(T)(string name, T value) {
|
||||
//bind(bindNameLookUp(name), value);
|
||||
//}
|
||||
|
||||
// This should be a template, but grrrr.
|
||||
void bind (const char[] name, const char[] value){ bind(bindNameLookUp(name), value); }
|
||||
void bind (const char[] name, int value){ bind(bindNameLookUp(name), value); }
|
||||
void bind (const char[] name, float value){ bind(bindNameLookUp(name), value); }
|
||||
void bind (const char[] name, const byte[] value){ bind(bindNameLookUp(name), value); }
|
||||
|
||||
void bind(int col, const char[] value){
|
||||
if(value is null) {
|
||||
if(sqlite3_bind_null(s, col) != SQLITE_OK)
|
||||
throw new DatabaseException("bind " ~ db.error());
|
||||
} else {
|
||||
if(sqlite3_bind_text(s, col, value.ptr, value.length, cast(void*)-1) != SQLITE_OK)
|
||||
throw new DatabaseException("bind " ~ db.error());
|
||||
}
|
||||
}
|
||||
|
||||
void bind(int col, float value){
|
||||
if(sqlite3_bind_double(s, col, value) != SQLITE_OK)
|
||||
throw new DatabaseException("bind " ~ db.error());
|
||||
}
|
||||
|
||||
void bind(int col, int value){
|
||||
if(sqlite3_bind_int(s, col, value) != SQLITE_OK)
|
||||
throw new DatabaseException("bind " ~ db.error());
|
||||
}
|
||||
|
||||
void bind(int col, const byte[] value){
|
||||
if(value is null) {
|
||||
if(sqlite3_bind_null(s, col) != SQLITE_OK)
|
||||
throw new DatabaseException("bind " ~ db.error());
|
||||
} else {
|
||||
if(sqlite3_bind_blob(s, col, cast(void*)value.ptr, value.length, cast(void*)-1) != SQLITE_OK)
|
||||
throw new DatabaseException("bind " ~ db.error());
|
||||
}
|
||||
}
|
||||
|
||||
void bind(int col, Variant v) {
|
||||
if(v.peek!int)
|
||||
bind(col, v.get!int);
|
||||
if(v.peek!string)
|
||||
bind(col, v.get!string);
|
||||
if(v.peek!float)
|
||||
bind(col, v.get!float);
|
||||
if(v.peek!(byte[]))
|
||||
bind(col, v.get!(byte[]));
|
||||
if(v.peek!(void*) && v.get!(void*) is null)
|
||||
bind(col, cast(string) null);
|
||||
}
|
||||
|
||||
~this(){
|
||||
if(!finalized)
|
||||
finalize();
|
||||
}
|
||||
|
||||
void finalize(){
|
||||
if(finalized)
|
||||
return;
|
||||
if(sqlite3_finalize(s) != SQLITE_OK)
|
||||
throw new DatabaseException("finalize " ~ db.error());
|
||||
finalized = true;
|
||||
}
|
||||
private:
|
||||
Variant[][] rows;
|
||||
char[][] columnNames;
|
||||
int length;
|
||||
int position;
|
||||
bool finalized;
|
||||
|
||||
sqlite3_stmt * s;
|
||||
|
||||
bool executed;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
new(size_t sz)
|
||||
{
|
||||
void* p;
|
||||
|
||||
p = std.c.stdlib.malloc(sz);
|
||||
if (!p)
|
||||
throw new OutOfMemoryError(__FILE__, __LINE__);
|
||||
GC.addRange(p, sz);
|
||||
return p;
|
||||
}
|
||||
|
||||
delete(void* p)
|
||||
{
|
||||
if (p)
|
||||
{ GC.removeRange(p);
|
||||
std.c.stdlib.free(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
version(sqlite_extended_metadata_available) {
|
||||
import std.typecons;
|
||||
struct ResultByDataObject {
|
||||
this(SqliteResult r, Tuple!(string, string)[string] mappings, Sqlite db) {
|
||||
result = r;
|
||||
this.db = db;
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
Tuple!(string, string)[string] mappings;
|
||||
|
||||
ulong length() { return result.length; }
|
||||
bool empty() { return result.empty; }
|
||||
void popFront() { result.popFront(); }
|
||||
DataObject front() {
|
||||
return new DataObject(db, result.front.toAA, mappings);
|
||||
}
|
||||
// would it be good to add a new() method? would be valid even if empty
|
||||
// it'd just fill in the ID's at random and allow you to do the rest
|
||||
|
||||
@disable this(this) { }
|
||||
|
||||
SqliteResult result;
|
||||
Sqlite db;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
extern(C) int callback(void* cb, int howmany, char** text, char** columns){
|
||||
if(cb is null)
|
||||
return 0;
|
||||
|
||||
void delegate(char[][char[]]) onEach = *cast(void delegate(char[][char[]])*)cb;
|
||||
|
||||
|
||||
char[][char[]] row;
|
||||
|
||||
for(int a = 0; a < howmany; a++){
|
||||
int b = std.c.string.strlen(columns[a]);
|
||||
char[] buf;
|
||||
buf.length = b;
|
||||
for(int c = 0; c < b; c++)
|
||||
buf[c] = columns[a][c];
|
||||
|
||||
int d = std.c.string.strlen(text[a]);
|
||||
char[] t;
|
||||
t.length = d;
|
||||
for(int c = 0; c < d; c++)
|
||||
t[c] = text[a][c];
|
||||
|
||||
row[buf.idup] = t;
|
||||
}
|
||||
|
||||
onEach(row);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
extern(C){
|
||||
typedef void sqlite3;
|
||||
typedef void sqlite3_stmt;
|
||||
int sqlite3_changes(sqlite3*);
|
||||
int sqlite3_close(sqlite3 *);
|
||||
int sqlite3_exec(
|
||||
sqlite3*, /* An open database */
|
||||
const(char) *sql, /* SQL to be evaluted */
|
||||
int function(void*,int,char**,char**), /* Callback function */
|
||||
void *, /* 1st argument to callback */
|
||||
char **errmsg /* Error msg written here */
|
||||
);
|
||||
|
||||
int sqlite3_open(
|
||||
const(char) *filename, /* Database filename (UTF-8) */
|
||||
sqlite3 **ppDb /* OUT: SQLite db handle */
|
||||
);
|
||||
|
||||
/+
|
||||
int sqlite3_open_v2(
|
||||
char *filename, /* Database filename (UTF-8) */
|
||||
sqlite3 **ppDb, /* OUT: SQLite db handle */
|
||||
int flags, /* Flags */
|
||||
char *zVfs /* Name of VFS module to use */
|
||||
);
|
||||
+/
|
||||
int sqlite3_prepare_v2(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const(char) *zSql, /* SQL statement, UTF-8 encoded */
|
||||
int nByte, /* Maximum length of zSql in bytes. */
|
||||
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
|
||||
char **pzTail /* OUT: Pointer to unused portion of zSql */
|
||||
);
|
||||
int sqlite3_finalize(sqlite3_stmt *pStmt);
|
||||
int sqlite3_step(sqlite3_stmt*);
|
||||
long sqlite3_last_insert_rowid(sqlite3*);
|
||||
|
||||
const int SQLITE_OK = 0;
|
||||
const int SQLITE_ROW = 100;
|
||||
const int SQLITE_DONE = 101;
|
||||
|
||||
const int SQLITE_INTEGER = 1; // int
|
||||
const int SQLITE_FLOAT = 2; // float
|
||||
const int SQLITE3_TEXT = 3; // char[]
|
||||
const int SQLITE_BLOB = 4; // byte[]
|
||||
const int SQLITE_NULL = 5; // void* = null
|
||||
|
||||
char *sqlite3_mprintf(const char*,...);
|
||||
|
||||
|
||||
int sqlite3_reset(sqlite3_stmt *pStmt);
|
||||
int sqlite3_clear_bindings(sqlite3_stmt*);
|
||||
int sqlite3_bind_parameter_index(sqlite3_stmt*, const(char) *zName);
|
||||
|
||||
int sqlite3_bind_blob(sqlite3_stmt*, int, void*, int n, void*);
|
||||
//int sqlite3_bind_blob(sqlite3_stmt*, int, void*, int n, void(*)(void*));
|
||||
int sqlite3_bind_double(sqlite3_stmt*, int, double);
|
||||
int sqlite3_bind_int(sqlite3_stmt*, int, int);
|
||||
int sqlite3_bind_null(sqlite3_stmt*, int);
|
||||
int sqlite3_bind_text(sqlite3_stmt*, int, const(char)*, int n, void*);
|
||||
//int sqlite3_bind_text(sqlite3_stmt*, int, char*, int n, void(*)(void*));
|
||||
|
||||
void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
|
||||
int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
|
||||
double sqlite3_column_double(sqlite3_stmt*, int iCol);
|
||||
int sqlite3_column_int(sqlite3_stmt*, int iCol);
|
||||
char *sqlite3_column_text(sqlite3_stmt*, int iCol);
|
||||
int sqlite3_column_type(sqlite3_stmt*, int iCol);
|
||||
char *sqlite3_column_name(sqlite3_stmt*, int N);
|
||||
|
||||
int sqlite3_column_count(sqlite3_stmt *pStmt);
|
||||
void sqlite3_free(void*);
|
||||
char *sqlite3_errmsg(sqlite3*);
|
||||
|
||||
const int SQLITE_OPEN_READONLY = 0x1;
|
||||
const int SQLITE_OPEN_READWRITE = 0x2;
|
||||
const int SQLITE_OPEN_CREATE = 0x4;
|
||||
const int SQLITE_CANTOPEN = 14;
|
||||
|
||||
|
||||
// will need these to enable support for DataObjects here
|
||||
const (char *)sqlite3_column_database_name(sqlite3_stmt*,int);
|
||||
const (char *)sqlite3_column_table_name(sqlite3_stmt*,int);
|
||||
const (char *)sqlite3_column_origin_name(sqlite3_stmt*,int);
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue