diff --git a/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java b/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java
index 056bad11..c545b19d 100644
--- a/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java
+++ b/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java
@@ -456,7 +456,7 @@ public class PostgreSQLMapStorage extends MapStorage {
doUpdate(c, "CREATE TABLE " + tableFaces + " (PlayerName VARCHAR(64) NOT NULL, TypeID INT NOT NULL, Image BYTEA, PRIMARY KEY(PlayerName, TypeID))");
doUpdate(c, "CREATE TABLE " + tableMarkerIcons + " (IconName VARCHAR(128) PRIMARY KEY NOT NULL, Image BYTEA)");
doUpdate(c, "CREATE TABLE " + tableMarkerFiles + " (FileName VARCHAR(128) PRIMARY KEY NOT NULL, Content BYTEA)");
- doUpdate(c, "CREATE TABLE " + tableStandaloneFiles + " (FileName VARCHAR(128) NOT NULL, ServerID BIGINT NOT NULL DEFAULT 0, Content TEXT, PRIMARY KEY (FileName, ServerID))");
+ doUpdate(c, "CREATE TABLE " + tableStandaloneFiles + " (FileName VARCHAR(128) NOT NULL, ServerID BIGINT NOT NULL DEFAULT 0, Content BYTEA, PRIMARY KEY (FileName, ServerID))");
doUpdate(c, "CREATE TABLE " + tableSchemaVersion + " (level INT PRIMARY KEY NOT NULL)");
doUpdate(c, "INSERT INTO " + tableSchemaVersion + " (level) VALUES (3)");
} catch (SQLException x) {
@@ -488,7 +488,7 @@ public class PostgreSQLMapStorage extends MapStorage {
c = getConnection();
doUpdate(c, "DELETE FROM " + tableStandaloneFiles + ";");
doUpdate(c, "ALTER TABLE " + tableStandaloneFiles + " DROP COLUMN Content;");
- doUpdate(c, "ALTER TABLE " + tableStandaloneFiles + " ADD COLUMN Content TEXT;");
+ doUpdate(c, "ALTER TABLE " + tableStandaloneFiles + " ADD COLUMN Content BYTEA;");
doUpdate(c, "UPDATE " + tableSchemaVersion + " SET level=3 WHERE level = 2;");
} catch (SQLException x) {
Log.severe("Error creating tables - " + x.getMessage());
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_access.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_access.php
new file mode 100644
index 00000000..ff4deea7
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_access.php
@@ -0,0 +1,9 @@
+
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_configuration.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_configuration.php
new file mode 100644
index 00000000..7678df14
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_configuration.php
@@ -0,0 +1,78 @@
+loginrequired && !$loggedin) {
+ echo "{ \"error\": \"login-required\" }";
+}
+else {
+ $uid = '[' . strtolower($userid) . ']';
+ $json->loggedin = $loggedin;
+ $wcnt = count($json->worlds);
+ $newworlds = array();
+ for($i = 0; $i < $wcnt; $i++) {
+ $w = $json->worlds[$i];
+ if($w->protected) {
+ $ss = stristr($worldaccess[$w->name], $uid);
+ if($ss !== false) {
+ $newworlds[] = $w;
+ }
+ else {
+ $w = null;
+ }
+ }
+ else {
+ $newworlds[] = $w;
+ }
+ if($w != null) {
+ $mcnt = count($w->maps);
+ $newmaps = array();
+ for($j = 0; $j < $mcnt; $j++) {
+ $m = $w->maps[$j];
+ if($m->protected) {
+ $ss = stristr($mapaccess[$w->name . '.' . $m->prefix], $uid);
+ if($ss !== false) {
+ $newmaps[] = $m;
+ }
+ }
+ else {
+ $newmaps[] = $m;
+ }
+ }
+ $w->maps = $newmaps;
+ }
+ }
+ $json->worlds = $newworlds;
+
+ echo json_encode($json);
+}
+cleanupDb();
+
+?>
+
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_funcs.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_funcs.php
new file mode 100644
index 00000000..08019635
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_funcs.php
@@ -0,0 +1,116 @@
+close();
+ $db = NULL;
+ }
+}
+
+function abortDb($errormsg) {
+ header('HTTP/1.0 500 Error');
+ echo "
500 Error
";
+ echo $errormsg;
+ cleanupDb();
+ exit;
+}
+
+function initDbIfNeeded() {
+ global $db, $dbhost, $dbuserid, $dbpassword, $dbname, $dbport;
+
+ $pos = strpos($dbname, '?');
+
+ if ($pos) {
+ $dbname = substr($dbname, 0, $pos);
+ }
+
+ if (!$db) {
+ $db = new PDO("pgsql:host=$dbhost;port=$dbport;dbname=$dbname", $dbuserid , $dbpassword, array(PDO::ATTR_PERSISTENT => true));
+ $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ if (!$db) {
+ abortDb("Error opening database");
+ }
+ }
+}
+
+
+function getStandaloneFileByServerId($fname, $sid) {
+ global $db, $dbprefix;
+
+ initDbIfNeeded();
+ $stmt = $db->prepare('SELECT Content from ' . $dbprefix . 'StandaloneFiles WHERE FileName=:fname AND ServerID=:sid');
+ $stmt->bindParam(':fname', $fname, PDO::PARAM_STR);
+ $stmt->bindParam(':sid', $sid, PDO::PARAM_INT);
+ $res = $stmt->execute();
+ $content = $stmt->fetch(PDO::FETCH_BOTH);
+ if ($res && $content) {
+ $rslt = stream_get_contents($content[0]); //stupid streams...
+ }
+ else {
+ $rslt = NULL;
+ }
+ $stmt->closeCursor();
+ return $rslt;
+}
+
+function getStandaloneFile($fname) {
+ global $serverid;
+
+ if (!isset($serverid)) {
+ $serverid = 0;
+ if(isset($_REQUEST['serverid'])) {
+ $serverid = $_REQUEST['serverid'];
+ }
+ }
+ return getStandaloneFileByServerId($fname, $serverid);
+}
+
+function updateStandaloneFileByServerId($fname, $sid, $content) {
+ global $db, $dbprefix;
+
+ initDbIfNeeded();
+ $stmt = $db->prepare('UPDATE ' . $dbprefix . 'StandaloneFiles SET Content=? WHERE FileName=? AND ServerID=?');
+ $stmt->bind_param('ssi', $content, $fname, $sid);
+ $res = $stmt->execute();
+ $stmt->close();
+ if (!$res) {
+ $res = insertStandaloneFileByServerId($fname, $sid, $content);
+ }
+ return $res;
+}
+
+function updateStandaloneFile($fname, $content) {
+ global $serverid;
+
+ if (!isset($serverid)) {
+ $serverid = 0;
+ if(isset($_REQUEST['serverid'])) {
+ $serverid = $_REQUEST['serverid'];
+ }
+ }
+ return updateStandaloneFileByServerId($fname, $serverid, $content);
+}
+
+function insertStandaloneFileByServerId($fname, $sid, $content) {
+ global $db, $dbprefix;
+
+ initDbIfNeeded();
+ $stmt = $db->prepare('INSERT INTO ' . $dbprefix . 'StandaloneFiles (Content,FileName,ServerID) VALUES (?,?,?);');
+ $res = $stmt->execute(array($content, $fname, $sid));
+ $stmt->close();
+ return $res;
+}
+
+function insertStandaloneFile($fname, $content) {
+ global $serverid;
+
+ if (!isset($serverid)) {
+ $serverid = 0;
+ if(isset($_REQUEST['serverid'])) {
+ $serverid = $_REQUEST['serverid'];
+ }
+ }
+ return insertStandaloneFileByServerId($fname, $serverid, $content);
+}
+
+?>
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_getlogin.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_getlogin.php
new file mode 100644
index 00000000..4619d8ff
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_getlogin.php
@@ -0,0 +1,8 @@
+
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_login.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_login.php
new file mode 100644
index 00000000..fc80539d
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_login.php
@@ -0,0 +1,75 @@
+
+
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_markers.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_markers.php
new file mode 100644
index 00000000..a73ed066
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_markers.php
@@ -0,0 +1,110 @@
+500 Error";
+ echo "Bad marker: " . $path;
+ exit();
+}
+
+$parts = explode("/", $path);
+
+if(($parts[0] != "faces") && ($parts[0] != "_markers_")) {
+ header('HTTP/1.0 500 Error');
+ echo "500 Error
";
+ echo "Bad marker: " . $path;
+ exit();
+}
+
+initDbIfNeeded();
+
+if ($parts[0] == "faces") {
+ if (count($parts) != 3) {
+ header('HTTP/1.0 500 Error');
+ echo "500 Error
";
+ echo "Bad face: " . $path;
+ cleanupDb();
+ exit();
+ }
+ $ft = 0;
+ if ($parts[1] == "8x8") {
+ $ft = 0;
+ }
+ else if ($parts[1] == '16x16') {
+ $ft = 1;
+ }
+ else if ($parts[1] == '32x32') {
+ $ft = 2;
+ }
+ else if ($parts[1] == 'body') {
+ $ft = 3;
+ }
+ $pn = explode(".", $parts[2]);
+ $stmt = $db->prepare('SELECT Image from ' . $dbprefix . 'Faces WHERE PlayerName=? AND TypeID=?');
+ $res = $stmt->execute(array($pn[0], $ft));
+ $timage = $stmt->fetch();
+ if ($res && $timage) {
+ header('Content-Type: image/png');
+ echo stream_get_contents($timage[0]);
+ }
+ else {
+ header('Location: ../images/blank.png');
+ }
+}
+else { // _markers_
+ $in = explode(".", $parts[1]);
+ $name = implode(".", array_slice($in, 0, count($in) - 1));
+ $ext = $in[count($in) - 1];
+ if (($ext == "json") && (strpos($name, "marker_") == 0)) {
+ $world = substr($name, 7);
+ $stmt = $db->prepare('SELECT Content from ' . $dbprefix . 'MarkerFiles WHERE FileName=?');
+ $res = $stmt->execute(array($world));
+ $timage = $stmt->fetch();
+ header('Content-Type: application/json');
+ if ($res && $timage) {
+ echo stream_get_contents($timage[0]); //PDO returns arrays, even for single colums, and bytea is returned as stream.
+ }
+ else {
+ echo "{ }";
+ }
+ }
+ else {
+ $stmt = $db->prepare('SELECT Image from ' . $dbprefix . 'MarkerIcons WHERE IconName=?');
+ $res = $stmt->execute(array($name));
+ $timage = $stmt->fetch();
+ if ($res && $timage) {
+ header('Content-Type: image/png');
+ echo stream_get_contents($timage[0]);
+ }
+ else {
+ header('Location: ../images/blank.png');
+ }
+ }
+}
+
+$stmt->closeCursor();
+
+cleanupDb();
+
+exit;
+?>
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_sendmessage.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_sendmessage.php
new file mode 100644
index 00000000..6aaa6281
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_sendmessage.php
@@ -0,0 +1,75 @@
+timestamp = $timestamp;
+ $data->ip = $_SERVER['REMOTE_ADDR'];
+ if(isset($_SESSION['userid'])) {
+ $uid = $_SESSION['userid'];
+ if(strcmp($uid, '-guest-')) {
+ $data->userid = $uid;
+ }
+ }
+ if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
+ $data->ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+ $content = getStandaloneFile('dynmap_webchat.json');
+ $gotold = false;
+ if (isset($content)) {
+ $old_messages = json_decode($content, true);
+ $gotold = true;
+ }
+
+ if(!empty($old_messages))
+ {
+ foreach($old_messages as $message)
+ {
+ if(($timestamp - $config['updaterate'] - 10000) < $message['timestamp'])
+ $new_messages[] = $message;
+ }
+ }
+ $new_messages[] = $data;
+
+ if ($gotold) {
+ updateStandaloneFile('dynmap_webchat.json', json_encode($new_messages));
+ }
+ else {
+ insertStandaloneFile('dynmap_webchat.json', json_encode($new_messages));
+ }
+
+ $_SESSION['lastchat'] = time()+$msginterval;
+ echo "{ \"error\" : \"none\" }";
+}
+elseif($_SERVER['REQUEST_METHOD'] == 'POST' && $lastchat > time())
+{
+ header('HTTP/1.1 403 Forbidden');
+}
+else {
+ echo "{ \"error\" : \"none\" }";
+}
+cleanupDb();
+
+?>
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_tiles.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_tiles.php
new file mode 100644
index 00000000..0e2cf7c8
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_tiles.php
@@ -0,0 +1,114 @@
+500 Error";
+ echo "Bad tile: " . $path;
+ exit();
+}
+
+$parts = explode("/", $path);
+
+if (count($parts) != 4) {
+ header('Location: ../images/blank.png');
+ cleanupDb();
+ exit;
+}
+
+$uid = '[' . strtolower($userid) . ']';
+
+$world = $parts[0];
+
+if(isset($worldaccess[$world])) {
+ $ss = stristr($worldaccess[$world], $uid);
+ if($ss === false) {
+ header('Location: ../images/blank.png');
+ cleanupDb();
+ exit;
+ }
+}
+$variant='STANDARD';
+
+ $prefix = $parts[1];
+ $plen = strlen($prefix);
+ if(($plen > 4) && (substr($prefix, $plen - 4) === "_day")) {
+ $prefix = substr($prefix, 0, $plen - 4);
+ $variant = 'DAY';
+ }
+ $mapid = $world . "." . $prefix;
+ if(isset($mapaccess[$mapid])) {
+ $ss = stristr($mapaccess[$mapid], $uid);
+ if($ss === false) {
+ header('Location: ../images/blank.png');
+ cleanupDb();
+ exit;
+ }
+ }
+
+$fparts = explode("_", $parts[3]);
+if (count($fparts) == 3) { // zoom_x_y
+ $zoom = strlen($fparts[0]);
+ $x = intval($fparts[1]);
+ $y = intval($fparts[2]);
+}
+else if (count($fparts) == 2) { // x_y
+ $zoom = 0;
+ $x = intval($fparts[0]);
+ $y = intval($fparts[1]);
+}
+else {
+ header('Location: ../images/blank.png');
+ cleanupDb();
+ exit;
+}
+initDbIfNeeded();
+
+$stmt = $db->prepare('SELECT t.Image,t.Format,t.HashCode,t.LastUpdate FROM ' . $dbprefix . 'Maps m JOIN ' . $dbprefix . 'Tiles t ON m.ID=t.MapID WHERE m.WorldID=? AND m.MapID=? AND m.Variant=? AND t.x=? AND t.y=? and t.zoom=?');
+$stmt->bindParam(1,$world, PDO::PARAM_STR);
+$stmt->bindParam(2,$prefix, PDO::PARAM_STR);
+$stmt->bindParam(3,$variant, PDO::PARAM_STR);
+$stmt->bindParam(4,$x, PDO::PARAM_INT);
+$stmt->bindParam(5,$y, PDO::PARAM_INT);
+$stmt->bindParam(6,$zoom, PDO::PARAM_INT);
+$res = $stmt->execute();
+list($timage, $format, $thash, $tlast) = $stmt->fetch();
+if ($res && $timage) {
+ if ($format == 0) {
+ header('Content-Type: image/png');
+ }
+ else {
+ header('Content-Type: image/jpeg');
+ }
+ header('ETag: \'' . $thash . '\'');
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $tlast/1000) . ' GMT');
+ echo stream_get_contents($timage);
+}
+else {
+ header('Location: ../images/blank.png');
+}
+
+$stmt->closeCursor();
+cleanupDb();
+
+exit;
+?>
diff --git a/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_update.php b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_update.php
new file mode 100644
index 00000000..45b9efb7
--- /dev/null
+++ b/DynmapCore/src/main/resources/extracted/web/standalone/PostgreSQL_update.php
@@ -0,0 +1,107 @@
+503 Database Unavailable";
+ echo 'Error reading database - ' . $fname . ' #' . $serverid;
+ cleanupDb();
+ exit;
+}
+
+
+if (!$loginenabled) {
+ echo $content;
+}
+else if(isset($json->loginrequired) && $json->loginrequired && !$loggedin) {
+ echo "{ \"error\": \"login-required\" }";
+}
+else {
+ $json = json_decode($content);
+ $json->loggedin = $loggedin;
+ if (isset($json->protected) && $json->protected) {
+ $ss = stristr($seeallmarkers, $uid);
+ if($ss === false) {
+ if(isset($playervisible[$useridlc])) {
+ $plist = $playervisible[$useridlc];
+ $pcnt = count($json->players);
+ for($i = 0; $i < $pcnt; $i++) {
+ $p = $json->players[$i];
+ if(!stristr($plist, '[' . $p->account . ']')) {
+ $p->world = "-some-other-bogus-world-";
+ $p->x = 0.0;
+ $p->y = 64.0;
+ $p->z = 0.0;
+ }
+ }
+ }
+ else {
+ $pcnt = count($json->players);
+ for($i = 0; $i < $pcnt; $i++) {
+ $p = $json->players[$i];
+ if(strcasecmp($userid, $p->account) != 0) {
+ $p->world = "-some-other-bogus-world-";
+ $p->x = 0.0;
+ $p->y = 64.0;
+ $p->z = 0.0;
+ }
+ }
+ }
+ }
+ }
+ echo json_encode($json);
+}
+cleanupDb();
+
+
+?>
+
diff --git a/README.md b/README.md
index 48785028..583f5ce8 100644
--- a/README.md
+++ b/README.md
@@ -17,5 +17,12 @@ The following target platforms are supported:
- Forge v1.11.2 - via Dynmap--forge-1.11.2.jar mod
- Forge v1.12.2 - via Dynmap--forge-1.12.2.jar mod
+# Data Storage
+Dynmap supports the following storage backends:
+- Flat files: The default for a new installation
+- SQLite
+- MySQL
+- PostgreSQL: EXPERIMENTAL
+
# Where to go for questions and discussions
I've just created a Reddit for the Dynmap family of mods/plugins - please give it a try - https://www.reddit.com/r/Dynmap/