From 53b65ab7e468a51f271a075d8bb6e05123e3e295 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Thu, 10 Mar 2022 14:30:02 -0600 Subject: [PATCH] Add microsoftsql storage type --- .../src/main/java/org/dynmap/DynmapCore.java | 7 +- .../storage/mssql/MicrosoftSQLMapStorage.java | 1088 +++++++++++++++++ .../resources/extracted/web/js/dynmaputils.js | 2 +- 3 files changed, 1093 insertions(+), 4 deletions(-) create mode 100644 DynmapCore/src/main/java/org/dynmap/storage/mssql/MicrosoftSQLMapStorage.java diff --git a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java index 316111bf..41fcfd67 100644 --- a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java +++ b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java @@ -1,9 +1,6 @@ package org.dynmap; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; @@ -60,6 +57,7 @@ import org.dynmap.storage.MapStorage; import org.dynmap.storage.aws_s3.AWSS3MapStorage; import org.dynmap.storage.filetree.FileTreeMapStorage; import org.dynmap.storage.mysql.MySQLMapStorage; +import org.dynmap.storage.mssql.MicrosoftSQLMapStorage; import org.dynmap.storage.mariadb.MariaDBMapStorage; import org.dynmap.storage.sqllte.SQLiteMapStorage; import org.dynmap.storage.postgresql.PostgreSQLMapStorage; @@ -447,6 +445,9 @@ public class DynmapCore implements DynmapCommonAPI { else if (storetype.equals("aws_s3")) { defaultStorage = new AWSS3MapStorage(); } + else if (storetype.equals("microsoftsql")) { + defaultStorage = new MicrosoftSQLMapStorage(); + } else { Log.severe("Invalid storage type for map data: " + storetype); return false; diff --git a/DynmapCore/src/main/java/org/dynmap/storage/mssql/MicrosoftSQLMapStorage.java b/DynmapCore/src/main/java/org/dynmap/storage/mssql/MicrosoftSQLMapStorage.java new file mode 100644 index 00000000..3e80c6ce --- /dev/null +++ b/DynmapCore/src/main/java/org/dynmap/storage/mssql/MicrosoftSQLMapStorage.java @@ -0,0 +1,1088 @@ +package org.dynmap.storage.mssql; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.Charset; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.dynmap.DynmapCore; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.MapType; +import org.dynmap.WebAuthManager; +import org.dynmap.MapType.ImageVariant; +import org.dynmap.PlayerFaces.FaceType; +import org.dynmap.storage.MapStorage; +import org.dynmap.storage.MapStorageTile; +import org.dynmap.storage.MapStorageTileEnumCB; +import org.dynmap.storage.MapStorageBaseTileEnumCB; +import org.dynmap.storage.MapStorageTileSearchEndCB; +import org.dynmap.utils.BufferInputStream; +import org.dynmap.utils.BufferOutputStream; + +public class MicrosoftSQLMapStorage extends MapStorage { + private String userid; + private String password; + protected String database; + protected String hostname; + private String prefix = ""; + protected String flags; + private String tableTiles; + private String tableMaps; + private String tableFaces; + private String tableMarkerIcons; + private String tableMarkerFiles; + private String tableStandaloneFiles; + private String tableSchemaVersion; + + protected int port; + private static final int POOLSIZE = 5; + private Connection[] cpool = new Connection[POOLSIZE]; + private int cpoolCount = 0; + private static final Charset UTF8 = Charset.forName("UTF-8"); + + public class StorageTile extends MapStorageTile { + private Integer mapkey; + private String uri; + protected StorageTile(DynmapWorld world, MapType map, int x, int y, + int zoom, ImageVariant var) { + super(world, map, x, y, zoom, var); + + mapkey = getMapKey(world, map, var); + + if (zoom > 0) { + uri = map.getPrefix() + var.variantSuffix + "/"+ (x >> 5) + "_" + (y >> 5) + "/" + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz".substring(0, zoom) + "_" + x + "_" + y + "." + map.getImageFormat().getFileExt(); + } + else { + uri = map.getPrefix() + var.variantSuffix + "/"+ (x >> 5) + "_" + (y >> 5) + "/" + x + "_" + y + "." + map.getImageFormat().getFileExt(); + } + } + + @Override + public boolean exists() { + if (mapkey == null) return false; + boolean rslt = false; + Connection c = null; + boolean err = false; + try { + c = getConnection(); + Statement stmt = c.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT HashCode FROM " + tableTiles + " WHERE MapID=" + mapkey + " AND x=" + x + " AND y=" + y + " AND zoom=" + zoom + ";"); + rslt = rs.next(); + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Tile exists error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return rslt; + } + + @Override + public boolean matchesHashCode(long hash) { + if (mapkey == null) return false; + boolean rslt = false; + Connection c = null; + boolean err = false; + try { + c = getConnection(); + Statement stmt = c.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT HashCode FROM " + tableTiles + " WHERE MapID=" + mapkey + " AND x=" + x + " AND y=" + y + " AND zoom=" + zoom + ";"); + if (rs.next()) { + long v = rs.getLong("HashCode"); + rslt = (v == hash); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Tile matches hash error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return rslt; + } + + @Override + public TileRead read() { + if (mapkey == null) return null; + TileRead rslt = null; + Connection c = null; + boolean err = false; + try { + c = getConnection(); + Statement stmt = c.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT HashCode,LastUpdate,Format,Image FROM " + tableTiles + " WHERE MapID=" + mapkey + " AND x=" + x + " AND y=" + y + " AND zoom=" + zoom + ";"); + if (rs.next()) { + rslt = new TileRead(); + rslt.hashCode = rs.getLong("HashCode"); + rslt.lastModified = rs.getLong("LastUpdate"); + rslt.format = MapType.ImageEncoding.fromOrd(rs.getInt("Format")); + byte[] img = rs.getBytes("Image"); + rslt.image = new BufferInputStream(img); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Tile read error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return rslt; + } + + @Override + public boolean write(long hash, BufferOutputStream encImage, long timestamp) { + if (mapkey == null) return false; + Connection c = null; + boolean err = false; + boolean exists = exists(); + // If delete, and doesn't exist, quit + if ((encImage == null) && (!exists)) return false; + + try { + c = getConnection(); + PreparedStatement stmt; + if (encImage == null) { // If delete + stmt = c.prepareStatement("DELETE FROM " + tableTiles + " WHERE MapID=? AND x=? and y=? AND zoom=?;"); + stmt.setInt(1, mapkey); + stmt.setInt(2, x); + stmt.setInt(3, y); + stmt.setInt(4, zoom); + } + else if (exists) { + stmt = c.prepareStatement("UPDATE " + tableTiles + " SET HashCode=?, LastUpdate=?, Format=?, Image=? WHERE MapID=? AND x=? and y=? AND zoom=?;"); + stmt.setLong(1, hash); + stmt.setLong(2, timestamp); + stmt.setInt(3, map.getImageFormat().getEncoding().ordinal()); + stmt.setBinaryStream(4, new BufferInputStream(encImage.buf, encImage.len), encImage.len); + stmt.setInt(5, mapkey); + stmt.setInt(6, x); + stmt.setInt(7, y); + stmt.setInt(8, zoom); + } + else { + stmt = c.prepareStatement("INSERT INTO " + tableTiles + " (MapID,x,y,zoom,HashCode,LastUpdate,Format,Image) VALUES (?,?,?,?,?,?,?,?);"); + stmt.setInt(1, mapkey); + stmt.setInt(2, x); + stmt.setInt(3, y); + stmt.setInt(4, zoom); + stmt.setLong(5, hash); + stmt.setLong(6, timestamp); + stmt.setInt(7, map.getImageFormat().getEncoding().ordinal()); + stmt.setBinaryStream(8, new BufferInputStream(encImage.buf, encImage.len), encImage.len); + } + stmt.executeUpdate(); + stmt.close(); + // Signal update for zoom out + if (zoom == 0) { + world.enqueueZoomOutUpdate(this); + } + } catch (SQLException x) { + logSQLException("Tile write error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return !err; + } + + @Override + public boolean getWriteLock() { + return MicrosoftSQLMapStorage.this.getWriteLock(uri); + } + + @Override + public void releaseWriteLock() { + MicrosoftSQLMapStorage.this.releaseWriteLock(uri); + } + + @Override + public boolean getReadLock(long timeout) { + return MicrosoftSQLMapStorage.this.getReadLock(uri, timeout); + } + + @Override + public void releaseReadLock() { + MicrosoftSQLMapStorage.this.releaseReadLock(uri); + } + + @Override + public void cleanup() { + } + + @Override + public String getURI() { + return uri; + } + + @Override + public void enqueueZoomOutUpdate() { + world.enqueueZoomOutUpdate(this); + } + + @Override + public MapStorageTile getZoomOutTile() { + int xx, yy; + int step = 1 << zoom; + if(x >= 0) + xx = x - (x % (2*step)); + else + xx = x + (x % (2*step)); + yy = -y; + if(yy >= 0) + yy = yy - (yy % (2*step)); + else + yy = yy + (yy % (2*step)); + yy = -yy; + return new StorageTile(world, map, xx, yy, zoom+1, var); + } + + @Override + public boolean equals(Object o) { + if (o instanceof StorageTile) { + StorageTile st = (StorageTile) o; + return uri.equals(st.uri); + } + return false; + } + + @Override + public int hashCode() { + return uri.hashCode(); + } + } + + public MicrosoftSQLMapStorage() { + } + + // MySQL specific driver check + protected boolean checkDriver() { + connectionString = "jdbc:sqlserver://" + hostname + ":" + port + ";databaseName=" + database + flags; + Log.info("Opening Microsoft SQL database " + hostname + ":" + port + ";databaseName=" + database + " as map store"); + + if(!hasClass("com.microsoft.sqlserver.jdbc.SQLServerDriver")){ + Log.severe("Microsoft SQL-JDBC classes not found - Microsoft SQL data source not usable"); + return false; + } + return true; + } + + @Override + public boolean init(DynmapCore core) { + if (!super.init(core)) { + return false; + } + database = core.configuration.getString("storage/database", "dynmap"); + hostname = core.configuration.getString("storage/hostname", "localhost"); + port = core.configuration.getInteger("storage/port", 1433); + userid = core.configuration.getString("storage/userid", "dynmap"); + password = core.configuration.getString("storage/password", "dynmap"); + prefix = core.configuration.getString("storage/prefix", ""); + flags = core.configuration.getString("storage/flags", ";integratedSecurity=true"); + tableTiles = prefix + "Tiles"; + tableMaps = prefix + "Maps"; + tableFaces = prefix + "Faces"; + tableMarkerIcons = prefix + "MarkerIcons"; + tableMarkerFiles = prefix + "MarkerFiles"; + tableStandaloneFiles = prefix + "StandaloneFiles"; + tableSchemaVersion = prefix + "SchemaVersion"; + + if (!checkDriver()) return false; + + // Initialize/update tables, if needed + if(!initializeTables()) { + return false; + } + return writeConfigPHP(core); + } + + private boolean hasClass(String classname){ + try{ + Class.forName(classname); + return true; + } catch (ClassNotFoundException cnfx){ + return false; + } + } + + private boolean writeConfigPHP(DynmapCore core) { + File cfgfile = new File(baseStandaloneDir, "MSSQL_config.php"); + if (!core.isInternalWebServerDisabled) { // If using internal server + cfgfile.delete(); // Zap file (in case we left junk from last time) + return true; + } + // During initial startup, this can happen before baseStandaloneDir is setup + if (!baseStandaloneDir.exists()) { + baseStandaloneDir.mkdirs(); + } + FileWriter fw = null; + try { + fw = new FileWriter(cfgfile); + fw.write("\n"); + } catch (IOException iox) { + Log.severe("Error writing MSSQL_config.php", iox); + return false; + } finally { + if (fw != null) { + try { fw.close(); } catch (IOException x) {} + } + } + return true; + } + private int getSchemaVersion() { + int ver = 0; + boolean err = false; + Connection c = null; + try { + c = getConnection(); // Get connection (create DB if needed) + DatabaseMetaData md = c.getMetaData(); + Log.info("Connected to " + md.getDatabaseProductName() + " v" + md.getDatabaseMajorVersion() + "." + md.getDatabaseMinorVersion()); + Statement stmt = c.createStatement(); + ResultSet rs = stmt.executeQuery( "SELECT level FROM " + tableSchemaVersion + ";"); + if (rs.next()) { + ver = rs.getInt("level"); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + err = true; + } finally { + if (c != null) { releaseConnection(c, err); } + } + return ver; + } + + private void doUpdate(Connection c, String sql) throws SQLException { + Statement stmt = c.createStatement(); + stmt.executeUpdate(sql); + stmt.close(); + } + + private HashMap mapKey = new HashMap(); + + private void doLoadMaps() { + Connection c = null; + boolean err = false; + + mapKey.clear(); + // Read the maps table - cache results + try { + c = getConnection(); + Statement stmt = c.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * from " + tableMaps + ";"); + while (rs.next()) { + int key = rs.getInt("ID"); + String worldID = rs.getString("WorldID"); + String mapID = rs.getString("MapID"); + String variant = rs.getString("Variant"); + long serverid = rs.getLong("ServerID"); + if (serverid == serverID) { // One of ours + mapKey.put(worldID + ":" + mapID + ":" + variant, key); + } + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Error loading map table", x); + err = true; + } finally { + releaseConnection(c, err); + c = null; + } + } + + private Integer getMapKey(DynmapWorld w, MapType mt, ImageVariant var) { + String id = w.getName() + ":" + mt.getPrefix() + ":" + var.toString(); + synchronized(mapKey) { + Integer k = mapKey.get(id); + if (k == null) { // No hit: new value so we need to add it to table + Connection c = null; + boolean err = false; + try { + c = getConnection(); + // Insert row + PreparedStatement stmt = c.prepareStatement("INSERT INTO " + tableMaps + " (WorldID,MapID,Variant,ServerID) VALUES (?, ?, ?, ?);"); + stmt.setString(1, w.getName()); + stmt.setString(2, mt.getPrefix()); + stmt.setString(3, var.toString()); + stmt.setLong(4, serverID); + stmt.executeUpdate(); + stmt.close(); + // Query key assigned + stmt = c.prepareStatement("SELECT ID FROM " + tableMaps + " WHERE WorldID = ? AND MapID = ? AND Variant = ? AND ServerID = ?;"); + stmt.setString(1, w.getName()); + stmt.setString(2, mt.getPrefix()); + stmt.setString(3, var.toString()); + stmt.setLong(4, serverID); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + k = rs.getInt("ID"); + mapKey.put(id, k); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Error updating Maps table", x); + err = true; + } finally { + releaseConnection(c, err); + } + } + + return k; + } + } + + private boolean initializeTables() { + Connection c = null; + boolean err = false; + int version = getSchemaVersion(); // Get the existing schema version for the DB (if any) + // If new, add our tables + if (version == 0) { + try { + Log.info("Initializing database schema"); + c = getConnection(); + doUpdate(c, "CREATE TABLE " + tableMaps + " (ID INTEGER IDENTITY(1,1) PRIMARY KEY, WorldID VARCHAR(64) NOT NULL, MapID VARCHAR(64) NOT NULL, Variant VARCHAR(16) NOT NULL, ServerID BIGINT NOT NULL DEFAULT 0)"); + doUpdate(c, "CREATE TABLE " + tableTiles + " (MapID INT NOT NULL, x INT NOT NULL, y INT NOT NULL, zoom INT NOT NULL, HashCode BIGINT NOT NULL, LastUpdate BIGINT NOT NULL, Format INT NOT NULL, Image varbinary(MAX), PRIMARY KEY(MapID, x, y, zoom))"); + doUpdate(c, "CREATE TABLE " + tableFaces + " (PlayerName VARCHAR(64) NOT NULL, TypeID INT NOT NULL, Image varbinary(MAX), PRIMARY KEY(PlayerName, TypeID))"); + doUpdate(c, "CREATE TABLE " + tableMarkerIcons + " (IconName VARCHAR(128) PRIMARY KEY NOT NULL, Image varbinary(MAX))"); + doUpdate(c, "CREATE TABLE " + tableMarkerFiles + " (FileName VARCHAR(128) PRIMARY KEY NOT NULL, Content varchar(MAX))"); + doUpdate(c, "CREATE TABLE " + tableStandaloneFiles + " (FileName VARCHAR(128) NOT NULL, ServerID BIGINT NOT NULL DEFAULT 0, Content varchar(MAX), PRIMARY KEY (FileName, ServerID))"); + doUpdate(c, "CREATE TABLE " + tableSchemaVersion + " (level INT PRIMARY KEY NOT NULL)"); + doUpdate(c, "INSERT INTO " + tableSchemaVersion + " (level) VALUES (5)"); + version = 5; // Initial - we have all the following updates already + } catch (SQLException x) { + logSQLException("Error creating tables", x); + err = true; + return false; + } finally { + releaseConnection(c, err); + c = null; + } + } + Log.info("Schema version = " + version); + // Load maps table - cache results + doLoadMaps(); + + return true; + } + + private Connection getConnection() throws SQLException { + Connection c = null; + synchronized (cpool) { + while (c == null) { + for (int i = 0; i < cpool.length; i++) { // See if available connection + if (cpool[i] != null) { // Found one + c = cpool[i]; + cpool[i] = null; + break; + } + } + if (c == null) { + if (cpoolCount < POOLSIZE) { // Still more we can have + c = DriverManager.getConnection(connectionString, userid, password); + configureConnection(c); + cpoolCount++; + } + else { + try { + cpool.wait(); + } catch (InterruptedException e) { + throw new SQLException("Interruped"); + } + } + } + } + } + return c; + } + + private static Connection configureConnection(Connection conn) throws SQLException { + return conn; + } + + private void releaseConnection(Connection c, boolean err) { + if (c == null) return; + synchronized (cpool) { + if (!err) { // Find slot to keep it in pool + for (int i = 0; i < POOLSIZE; i++) { + if (cpool[i] == null) { + cpool[i] = c; + c = null; // Mark it recovered (no close needed + cpool.notifyAll(); + break; + } + } + } + if (c != null) { // If broken, just toss it + try { c.close(); } catch (SQLException x) {} + cpoolCount--; // And reduce count + cpool.notifyAll(); + } + } + } + + @Override + public MapStorageTile getTile(DynmapWorld world, MapType map, int x, int y, + int zoom, ImageVariant var) { + return new StorageTile(world, map, x, y, zoom, var); + } + + @Override + public MapStorageTile getTile(DynmapWorld world, String uri) { + String[] suri = uri.split("/"); + if (suri.length < 2) return null; + String mname = suri[0]; // Map URI - might include variant + MapType mt = null; + ImageVariant imgvar = null; + // Find matching map type and image variant + for (int mti = 0; (mt == null) && (mti < world.maps.size()); mti++) { + MapType type = world.maps.get(mti); + ImageVariant[] var = type.getVariants(); + for (int ivi = 0; (imgvar == null) && (ivi < var.length); ivi++) { + if (mname.equals(type.getPrefix() + var[ivi].variantSuffix)) { + mt = type; + imgvar = var[ivi]; + } + } + } + if (mt == null) { // Not found? + return null; + } + // Now, take the last section and parse out coordinates and zoom + String fname = suri[suri.length-1]; + String[] coord = fname.split("[_\\.]"); + if (coord.length < 3) { // 3 or 4 + return null; + } + int zoom = 0; + int x, y; + try { + if (coord[0].charAt(0) == 'z') { + zoom = coord[0].length(); + x = Integer.parseInt(coord[1]); + y = Integer.parseInt(coord[2]); + } + else { + x = Integer.parseInt(coord[0]); + y = Integer.parseInt(coord[1]); + } + return getTile(world, mt, x, y, zoom, imgvar); + } catch (NumberFormatException nfx) { + return null; + } + } + + @Override + public void enumMapTiles(DynmapWorld world, MapType map, + MapStorageTileEnumCB cb) { + List mtlist; + + if (map != null) { + mtlist = Collections.singletonList(map); + } + else { // Else, add all directories under world directory (for maps) + mtlist = new ArrayList(world.maps); + } + for (MapType mt : mtlist) { + ImageVariant[] vars = mt.getVariants(); + for (ImageVariant var : vars) { + processEnumMapTiles(world, mt, var, cb, null, null); + } + } + } + @Override + public void enumMapBaseTiles(DynmapWorld world, MapType map, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) { + List mtlist; + + if (map != null) { + mtlist = Collections.singletonList(map); + } + else { // Else, add all directories under world directory (for maps) + mtlist = new ArrayList(world.maps); + } + for (MapType mt : mtlist) { + ImageVariant[] vars = mt.getVariants(); + for (ImageVariant var : vars) { + processEnumMapTiles(world, mt, var, null, cbBase, cbEnd); + } + } + } + private void processEnumMapTiles(DynmapWorld world, MapType map, ImageVariant var, MapStorageTileEnumCB cb, MapStorageBaseTileEnumCB cbBase, MapStorageTileSearchEndCB cbEnd) { + Connection c = null; + boolean err = false; + Integer mapkey = getMapKey(world, map, var); + if (mapkey == null) { + if(cbEnd != null) + cbEnd.searchEnded(); + return; + } + try { + c = getConnection(); + // Query tiles for given mapkey + Statement stmt = c.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT x,y,zoom,Format FROM " + tableTiles + " WHERE MapID=" + mapkey + ";"); + while (rs.next()) { + StorageTile st = new StorageTile(world, map, rs.getInt("x"), rs.getInt("y"), rs.getInt("zoom"), var); + final MapType.ImageEncoding encoding = MapType.ImageEncoding.fromOrd(rs.getInt("Format")); + if(cb != null) + cb.tileFound(st, encoding); + if(cbBase != null && st.zoom == 0) + cbBase.tileFound(st, encoding); + st.cleanup(); + } + if(cbEnd != null) + cbEnd.searchEnded(); + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Tile enum error", x); + err = true; + } finally { + releaseConnection(c, err); + } + } + + @Override + public void purgeMapTiles(DynmapWorld world, MapType map) { + List mtlist; + + if (map != null) { + mtlist = Collections.singletonList(map); + } + else { // Else, add all directories under world directory (for maps) + mtlist = new ArrayList(world.maps); + } + for (MapType mt : mtlist) { + ImageVariant[] vars = mt.getVariants(); + for (ImageVariant var : vars) { + processPurgeMapTiles(world, mt, var); + } + } + } + private void processPurgeMapTiles(DynmapWorld world, MapType map, ImageVariant var) { + Connection c = null; + boolean err = false; + Integer mapkey = getMapKey(world, map, var); + if (mapkey == null) return; + try { + c = getConnection(); + // Query tiles for given mapkey + Statement stmt = c.createStatement(); + // Limit delete to 1000 at a time (avoid locking whole table) + while (stmt.executeUpdate("DELETE FROM " + tableTiles + " WHERE MapID=" + mapkey + " LIMIT 1000;") > 0) { + } + stmt.close(); + } catch (SQLException x) { + logSQLException("Tile purge error", x); + err = true; + } finally { + releaseConnection(c, err); + } + } + + @Override + public boolean setPlayerFaceImage(String playername, FaceType facetype, + BufferOutputStream encImage) { + Connection c = null; + boolean err = false; + boolean exists = hasPlayerFaceImage(playername, facetype); + // If delete, and doesn't exist, quit + if ((encImage == null) && (!exists)) return false; + + try { + c = getConnection(); + PreparedStatement stmt; + if (encImage == null) { // If delete + stmt = c.prepareStatement("DELETE FROM " + tableFaces + " WHERE PlayerName=? AND TypeIDx=?;"); + stmt.setString(1, playername); + stmt.setInt(2, facetype.typeID); + } + else if (exists) { + stmt = c.prepareStatement("UPDATE " + tableFaces + " SET Image=? WHERE PlayerName=? AND TypeID=?;"); + stmt.setBinaryStream(1, new BufferInputStream(encImage.buf, encImage.len), encImage.len); + stmt.setString(2, playername); + stmt.setInt(3, facetype.typeID); + } + else { + stmt = c.prepareStatement("INSERT INTO " + tableFaces + " (PlayerName,TypeID,Image) VALUES (?,?,?);"); + stmt.setString(1, playername); + stmt.setInt(2, facetype.typeID); + stmt.setBinaryStream(3, new BufferInputStream(encImage.buf, encImage.len), encImage.len); + } + stmt.executeUpdate(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Face write error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return !err; + } + + @Override + public BufferInputStream getPlayerFaceImage(String playername, + FaceType facetype) { + Connection c = null; + boolean err = false; + BufferInputStream image = null; + try { + c = getConnection(); + PreparedStatement stmt = c.prepareStatement("SELECT Image FROM " + tableFaces + " WHERE PlayerName=? AND TypeID=?;"); + stmt.setString(1, playername); + stmt.setInt(2, facetype.typeID); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + byte[] img = rs.getBytes("Image"); + image = new BufferInputStream(img); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Face reqd error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return image; + } + + @Override + public boolean hasPlayerFaceImage(String playername, FaceType facetype) { + Connection c = null; + boolean err = false; + boolean exists = false; + try { + c = getConnection(); + PreparedStatement stmt = c.prepareStatement("SELECT TypeID FROM " + tableFaces + " WHERE PlayerName=? AND TypeID=?;"); + stmt.setString(1, playername); + stmt.setInt(2, facetype.typeID); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + exists = true; + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Face exists error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return exists; + } + + @Override + public boolean setMarkerImage(String markerid, BufferOutputStream encImage) { + Connection c = null; + boolean err = false; + PreparedStatement stmt = null; + ResultSet rs = null; + + try { + c = getConnection(); + boolean exists = false; + stmt = c.prepareStatement("SELECT IconName FROM " + tableMarkerIcons + " WHERE IconName=?;"); + stmt.setString(1, markerid); + rs = stmt.executeQuery(); + if (rs.next()) { + exists = true; + } + rs.close(); + rs = null; + stmt.close(); + stmt = null; + if (encImage == null) { // If delete + // If delete, and doesn't exist, quit + if (!exists) return false; + stmt = c.prepareStatement("DELETE FROM " + tableMarkerIcons + " WHERE IconName=?;"); + stmt.setString(1, markerid); + stmt.executeUpdate(); + } + else if (exists) { + stmt = c.prepareStatement("UPDATE " + tableMarkerIcons + " SET Image=? WHERE IconName=?;"); + stmt.setBinaryStream(1, new BufferInputStream(encImage.buf, encImage.len), encImage.len); + stmt.setString(2, markerid); + } + else { + stmt = c.prepareStatement("INSERT INTO " + tableMarkerIcons + " (IconName,Image) VALUES (?,?);"); + stmt.setString(1, markerid); + stmt.setBinaryStream(2, new BufferInputStream(encImage.buf, encImage.len), encImage.len); + } + stmt.executeUpdate(); + } catch (SQLException x) { + logSQLException("Marker write error", x); + err = true; + } finally { + if (rs != null) { try { rs.close(); } catch (SQLException sx) {} } + if (stmt != null) { try { stmt.close(); } catch (SQLException sx) {} } + releaseConnection(c, err); + } + return !err; + } + + @Override + public BufferInputStream getMarkerImage(String markerid) { + Connection c = null; + boolean err = false; + BufferInputStream image = null; + try { + c = getConnection(); + PreparedStatement stmt = c.prepareStatement("SELECT Image FROM " + tableMarkerIcons + " WHERE IconName=?;"); + stmt.setString(1, markerid); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + byte[] img = rs.getBytes("Image"); + image = new BufferInputStream(img); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Marker read error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return image; + } + + @Override + public boolean setMarkerFile(String world, String content) { + Connection c = null; + boolean err = false; + PreparedStatement stmt = null; + ResultSet rs = null; + try { + c = getConnection(); + boolean exists = false; + stmt = c.prepareStatement("SELECT FileName FROM " + tableMarkerFiles + " WHERE FileName=?;"); + stmt.setString(1, world); + rs = stmt.executeQuery(); + if (rs.next()) { + exists = true; + } + rs.close(); + rs = null; + stmt.close(); + stmt = null; + if (content == null) { // If delete + // If delete, and doesn't exist, quit + if (!exists) return false; + stmt = c.prepareStatement("DELETE FROM " + tableMarkerFiles + " WHERE FileName=?;"); + stmt.setString(1, world); + stmt.executeUpdate(); + } + else if (exists) { + stmt = c.prepareStatement("UPDATE " + tableMarkerFiles + " SET Content=? WHERE FileName=?;"); + stmt.setBytes(1, content.getBytes(UTF8)); + stmt.setString(2, world); + } + else { + stmt = c.prepareStatement("INSERT INTO " + tableMarkerFiles + " (FileName,Content) VALUES (?,?);"); + stmt.setString(1, world); + stmt.setBytes(2, content.getBytes(UTF8)); + } + stmt.executeUpdate(); + } catch (SQLException x) { + logSQLException("Marker file write error", x); + err = true; + } finally { + if (rs != null) { try { rs.close(); } catch (SQLException sx) {} } + if (stmt != null) { try { stmt.close(); } catch (SQLException sx) {} } + releaseConnection(c, err); + } + return !err; + } + + @Override + public String getMarkerFile(String world) { + Connection c = null; + boolean err = false; + String content = null; + try { + c = getConnection(); + PreparedStatement stmt = c.prepareStatement("SELECT Content FROM " + tableMarkerFiles + " WHERE FileName=?;"); + stmt.setString(1, world); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + byte[] img = rs.getBytes("Content"); + content = new String(img, UTF8); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Marker file read error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return content; + } + + @Override + // External web server only + public String getMarkersURI(boolean login_enabled) { + return "standalone/MSSQL_markers.php?marker="; + } + + @Override + // External web server only + public String getTilesURI(boolean login_enabled) { + return "standalone/MSSQL_tiles.php?tile="; + } + + @Override + // External web server only + public String getConfigurationJSONURI(boolean login_enabled) { + return "standalone/MSSQL_configuration.php"; // ?serverid={serverid}"; + } + + @Override + // External web server only + public String getUpdateJSONURI(boolean login_enabled) { + return "standalone/MSSQL_update.php?world={world}&ts={timestamp}"; // &serverid={serverid}"; + } + + @Override + // External web server only + public String getSendMessageURI() { + return "standalone/MSSQL_sendmessage.php"; + } + + @Override + public BufferInputStream getStandaloneFile(String fileid) { + Connection c = null; + boolean err = false; + BufferInputStream content = null; + try { + c = getConnection(); + PreparedStatement stmt = c.prepareStatement("SELECT Content FROM " + tableStandaloneFiles + " WHERE FileName=? AND ServerID=?;"); + stmt.setString(1, fileid); + stmt.setLong(2, serverID); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + byte[] img = rs.getBytes("Content"); + content = new BufferInputStream(img); + } + rs.close(); + stmt.close(); + } catch (SQLException x) { + logSQLException("Standalone file read error", x); + err = true; + } finally { + releaseConnection(c, err); + } + return content; + } + + @Override + public boolean setStandaloneFile(String fileid, BufferOutputStream content) { + Connection c = null; + boolean err = false; + PreparedStatement stmt = null; + ResultSet rs = null; + try { + c = getConnection(); + boolean exists = false; + stmt = c.prepareStatement("SELECT FileName FROM " + tableStandaloneFiles + " WHERE FileName=? AND ServerID=?;"); + stmt.setString(1, fileid); + stmt.setLong(2, serverID); + rs = stmt.executeQuery(); + if (rs.next()) { + exists = true; + } + rs.close(); + rs = null; + stmt.close(); + stmt = null; + if (content == null) { // If delete + // If delete, and doesn't exist, quit + if (!exists) return true; + stmt = c.prepareStatement("DELETE FROM " + tableStandaloneFiles + " WHERE FileName=? AND ServerID=?;"); + stmt.setString(1, fileid); + stmt.setLong(2, serverID); + stmt.executeUpdate(); + } + else if (exists) { + stmt = c.prepareStatement("UPDATE " + tableStandaloneFiles + " SET Content=? WHERE FileName=? AND ServerID=?;"); + stmt.setBinaryStream(1, new BufferInputStream(content.buf, content.len), content.len); + stmt.setString(2, fileid); + stmt.setLong(3, serverID); + } + else { + stmt = c.prepareStatement("INSERT INTO " + tableStandaloneFiles + " (FileName,ServerID,Content) VALUES (?,?,?);"); + stmt.setString(1, fileid); + stmt.setLong(2, serverID); + stmt.setBinaryStream(3, new BufferInputStream(content.buf, content.len), content.len); + } + stmt.executeUpdate(); + } catch (SQLException x) { + logSQLException("Standalone file write error", x); + err = true; + } finally { + if (rs != null) { try { rs.close(); } catch (SQLException sx) {} } + if (stmt != null) { try { stmt.close(); } catch (SQLException sx) {} } + releaseConnection(c, err); + } + return !err; + } + @Override + public boolean wrapStandaloneJSON(boolean login_enabled) { + return false; + } + @Override + public boolean wrapStandalonePHP() { + return false; + } + @Override + // External web server only + public String getStandaloneLoginURI() { + return "standalone/MSSQL_login.php"; + } + @Override + // External web server only + public String getStandaloneRegisterURI() { + return "standalone/MSSQL_register.php"; + } + @Override + public void setLoginEnabled(DynmapCore core) { + writeConfigPHP(core); + } + +} diff --git a/DynmapCore/src/main/resources/extracted/web/js/dynmaputils.js b/DynmapCore/src/main/resources/extracted/web/js/dynmaputils.js index 80ece268..936664aa 100644 --- a/DynmapCore/src/main/resources/extracted/web/js/dynmaputils.js +++ b/DynmapCore/src/main/resources/extracted/web/js/dynmaputils.js @@ -237,7 +237,7 @@ var DynmapTileLayer = L.TileLayer.extend({ // Some helper functions. zoomprefix: function(amount) { - return 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'.substr(0, amount); + return ' zzzzzzzzzzzzzzzzzzzzzzzzzz'.substr(0, amount); }, getTileInfo: function(coords) {