Merge branch 'general-devel' into general-devel
This commit is contained in:
commit
a52aab67bf
46 changed files with 1213 additions and 1046 deletions
6
.github/workflows/ci-otapi3-nuget.yml
vendored
6
.github/workflows/ci-otapi3-nuget.yml
vendored
|
|
@ -11,13 +11,13 @@ jobs:
|
|||
environment: release
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 6.0.400
|
||||
dotnet-version: 9.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
|
|
|||
24
.github/workflows/ci-otapi3.yml
vendored
24
.github/workflows/ci-otapi3.yml
vendored
|
|
@ -7,13 +7,13 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '6.0.100'
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test
|
||||
|
|
@ -25,13 +25,13 @@ jobs:
|
|||
arch: ["win-x64", "osx-x64", "linux-x64", "linux-arm64", "linux-arm"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '6.0.100'
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install msgfmt
|
||||
run: sudo apt-get install -y gettext
|
||||
|
|
@ -39,27 +39,27 @@ jobs:
|
|||
- name: Produce installer
|
||||
run: |
|
||||
cd TShockInstaller
|
||||
dotnet publish -r ${{ matrix.arch }} -f net6.0 -c Release -p:PublishSingleFile=true --self-contained true
|
||||
dotnet publish -r ${{ matrix.arch }} -f net9.0 -c Release -p:PublishSingleFile=true --self-contained true
|
||||
|
||||
- name: Produce build
|
||||
run: |
|
||||
cd TShockLauncher
|
||||
dotnet publish -r ${{ matrix.arch }} -f net6.0 -c Release -p:PublishSingleFile=true --self-contained false
|
||||
dotnet publish -r ${{ matrix.arch }} -f net9.0 -c Release -p:PublishSingleFile=true --self-contained false
|
||||
|
||||
- name: Chmod scripts
|
||||
if: ${{ matrix.arch != 'win-x64' }}
|
||||
run: |
|
||||
chmod +x TShockLauncher/bin/Release/net6.0/${{ matrix.arch }}/publish/TShock.Server
|
||||
chmod +x TShockLauncher/bin/Release/net9.0/${{ matrix.arch }}/publish/TShock.Server
|
||||
|
||||
- name: Copy installer
|
||||
run: |
|
||||
cp TShockInstaller/bin/Release/net6.0/${{ matrix.arch }}/publish/* TShockLauncher/bin/Release/net6.0/${{ matrix.arch }}/publish/
|
||||
cp TShockInstaller/bin/Release/net9.0/${{ matrix.arch }}/publish/* TShockLauncher/bin/Release/net9.0/${{ matrix.arch }}/publish/
|
||||
|
||||
# preserve file perms: https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files
|
||||
- name: Tarball artifact (non-Windows)
|
||||
if: ${{ matrix.arch != 'win-x64' }}
|
||||
run: |
|
||||
cd TShockLauncher/bin/Release/net6.0/${{ matrix.arch }}/publish/
|
||||
cd TShockLauncher/bin/Release/net9.0/${{ matrix.arch }}/publish/
|
||||
tar -cvf ../../../../../../TShock-Beta-${{ matrix.arch }}-Release.tar *
|
||||
|
||||
- name: Upload artifact (non-Windows)
|
||||
|
|
@ -74,4 +74,4 @@ jobs:
|
|||
if: ${{ matrix.arch == 'win-x64' }}
|
||||
with:
|
||||
name: TShock-Beta-${{ matrix.arch }}-Release
|
||||
path: TShockLauncher/bin/Release/net6.0/${{ matrix.arch }}/publish/
|
||||
path: TShockLauncher/bin/Release/net9.0/${{ matrix.arch }}/publish/
|
||||
|
|
|
|||
13
.github/workflows/wiki-notify.yml
vendored
Normal file
13
.github/workflows/wiki-notify.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
name: Wiki Changed Discord Notification
|
||||
|
||||
on:
|
||||
gollum
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: 'oznu/gh-wiki-edit-discord-notification@dfc866fd048f04c239ad113eef3c6c73504d333e'
|
||||
with:
|
||||
discord-webhook-url: ${{ secrets.DISCORD_WEBHOOK_WIKI_EDIT }}
|
||||
ignore-collaborators: false
|
||||
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
|
|
@ -9,12 +9,9 @@
|
|||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/TShockLauncher/bin/Debug/net6.0/TShock.Run.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceFolder}/TShockLauncher/bin/Debug/net6.0/TShock.dll",
|
||||
},
|
||||
"program": "${workspaceFolder}/TShockLauncher/bin/Debug/net9.0/TShock.Server.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/TShockLauncher/bin/Debug/net6.0/",
|
||||
"cwd": "${workspaceFolder}/TShockLauncher/bin/Debug/net9.0/",
|
||||
"console": "integratedTerminal",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
|
|
|
|||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
|
@ -35,7 +35,7 @@
|
|||
{
|
||||
"label": "Remote Publish",
|
||||
"options": {
|
||||
"cwd": "TShockLauncher/bin/Debug/net6.0/linux-arm64"
|
||||
"cwd": "TShockLauncher/bin/Debug/net9.0/linux-arm64"
|
||||
},
|
||||
"command": "C:\\Program Files\\PuTTY\\pscp.exe",
|
||||
"type": "process",
|
||||
|
|
|
|||
10
Dockerfile
10
Dockerfile
|
|
@ -1,7 +1,7 @@
|
|||
# TARGETPLATFORM and BUILDPLATFORM are automatically filled in by Docker buildx.
|
||||
# They should not be set in the global scope manually.
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:6.0 AS builder
|
||||
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:9.0 AS builder
|
||||
|
||||
# Copy build context
|
||||
WORKDIR /TShock
|
||||
|
|
@ -27,10 +27,14 @@ RUN \
|
|||
*) echo "Error: Unsupported platform ${TARGETPLATFORM}" && exit 1 \
|
||||
;; \
|
||||
esac && \
|
||||
dotnet publish -o output/ -r "${ARCH}" -v m -f net6.0 -c Release -p:PublishSingleFile=true --self-contained false
|
||||
dotnet publish -o output/ -r "${ARCH}" -v m -f net9.0 -c Release -p:PublishSingleFile=true --self-contained false
|
||||
|
||||
# Runtime image
|
||||
FROM --platform=${TARGETPLATFORM} mcr.microsoft.com/dotnet/runtime:6.0 AS runner
|
||||
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS linux_base
|
||||
FROM mcr.microsoft.com/dotnet/runtime:9.0-nanoserver-ltsc2022 AS windows_base
|
||||
|
||||
FROM ${TARGETOS}_base AS final
|
||||
|
||||
WORKDIR /server
|
||||
COPY --from=builder /TShock/TShockLauncher/output ./
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ If you want to run the `TShockLauncher` (which runs a server), run:
|
|||
To produce a packaged release (suitable for distribution), run:
|
||||
|
||||
1. `cd TShockLauncher`
|
||||
1. `dotnet publish -r win-x64 -f net6.0 -c Release -p:PublishSingleFile=true --self-contained false`
|
||||
1. `dotnet publish -r win-x64 -f net9.0 -c Release -p:PublishSingleFile=true --self-contained false`
|
||||
|
||||
Note that in this example, you'd be building for `win-x64`. You can build for `win-x64`, `osx-x64`, `linux-x64`, `linux-arm64`, `linux-arm`. Your release will be in the `TShockLauncher/bin/Release/net6.0/` folder under the architecture you specified.
|
||||
Note that in this example, you'd be building for `win-x64`. You can build for `win-x64`, `osx-x64`, `linux-x64`, `linux-arm64`, `linux-arm`. Your release will be in the `TShockLauncher/bin/Release/net9.0/` folder under the architecture you specified.
|
||||
|
||||
### Working with Terraria
|
||||
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ TShock是为泰拉瑞亚服务器和社区开发的一个工具箱。这个工
|
|||
如果要生成打包后的发行版,运行:
|
||||
|
||||
1. `cd TShockLauncher`
|
||||
1. `dotnet publish -r win-x64 -f net6.0 -c Release -p:PublishSingleFile=true --self-contained false`
|
||||
1. `dotnet publish -r win-x64 -f net9.0 -c Release -p:PublishSingleFile=true --self-contained false`
|
||||
|
||||
注意在这个例子中你将会生成`win-x64`架构的版本。你也可以生成`win-x64`、`osx-x64`、`linux-x64`、`linux-arm64`、`linux-arm`的版本。你可以在`TShockLauncher/bin/Release/net6.0/`文件夹下对应架构的文件夹里找到生成后的发行版。
|
||||
注意在这个例子中你将会生成`win-x64`架构的版本。你也可以生成`win-x64`、`osx-x64`、`linux-x64`、`linux-arm64`、`linux-arm`的版本。你可以在`TShockLauncher/bin/Release/net9.0/`文件夹下对应架构的文件夹里找到生成后的发行版。
|
||||
|
||||
### 跟泰拉瑞亚本体代码交互
|
||||
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ namespace TShockAPI.Configuration
|
|||
#region MySQL Settings
|
||||
|
||||
/// <summary>The type of database to use when storing data (either "sqlite" or "mysql").</summary>
|
||||
[Description("The type of database to use when storing data (either \"sqlite\" or \"mysql\").")]
|
||||
[Description("The type of database to use when storing data (either \"sqlite\", \"mysql\" or \"postgres\").")]
|
||||
public string StorageType = "sqlite";
|
||||
|
||||
/// <summary>The path of sqlite db.</summary>
|
||||
|
|
@ -552,6 +552,22 @@ namespace TShockAPI.Configuration
|
|||
[Description("The password used when connecting to a MySQL database.")]
|
||||
public string MySqlPassword = "";
|
||||
|
||||
///<summary>The Postgres hostname and port to direct connections to.</summary>
|
||||
[Description("The Postgres hostname and port to direct connections to.")]
|
||||
public string PostgresHost = "";
|
||||
|
||||
/// <summary>The database name to connect to when using Postgres as the database type.</summary>
|
||||
[Description("The database name to connect to when using Postgres as the database type.")]
|
||||
public string PostgresDbName = "";
|
||||
|
||||
/// <summary>The username used when connecting to a Postgres database.</summary>
|
||||
[Description("The username used when connecting to a Postgres database.")]
|
||||
public string PostgresUsername = "";
|
||||
|
||||
/// <summary>The password used when connecting to a Postgres database.</summary>
|
||||
[Description("The password used when connecting to a Postgres database.")]
|
||||
public string PostgresPassword = "";
|
||||
|
||||
/// <summary>Whether or not to save logs to the SQL database instead of a text file.</summary>
|
||||
[Description("Whether or not to save logs to the SQL database instead of a text file.\nDefault = false.")]
|
||||
public bool UseSqlLogs = false;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ using System.Collections.Generic;
|
|||
using System.Data;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System.Collections.ObjectModel;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -68,10 +69,9 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("Date", MySqlDbType.Int64),
|
||||
new SqlColumn("Expiration", MySqlDbType.Int64)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder)new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
var creator = new SqlTableCreator(db, db.GetSqlQueryBuilder());
|
||||
|
||||
try
|
||||
{
|
||||
creator.EnsureTableStructure(table);
|
||||
|
|
@ -105,15 +105,12 @@ namespace TShockAPI.DB
|
|||
/// </summary>
|
||||
public void TryConvertBans()
|
||||
{
|
||||
int res;
|
||||
if (database.GetSqlType() == SqlType.Mysql)
|
||||
int res = database.GetSqlType() switch
|
||||
{
|
||||
res = database.QueryScalar<int>("SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = @0 and table_name = 'Bans'", TShock.Config.Settings.MySqlDbName);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = database.QueryScalar<int>("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name = 'Bans'");
|
||||
}
|
||||
SqlType.Mysql => database.QueryScalar<int>("SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = @0 and table_name = 'Bans'", TShock.Config.Settings.MySqlDbName),
|
||||
SqlType.Sqlite => database.QueryScalar<int>("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name = 'Bans'"),
|
||||
SqlType.Postgres => database.QueryScalar<int>("SELECT COUNT(table_name) FROM information_schema.tables WHERE table_name = 'Bans'"),
|
||||
};
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
|
|
@ -300,16 +297,13 @@ namespace TShockAPI.DB
|
|||
return new AddBanResult { Message = message };
|
||||
}
|
||||
|
||||
string query = "INSERT INTO PlayerBans (Identifier, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4);";
|
||||
|
||||
if (database.GetSqlType() == SqlType.Mysql)
|
||||
string query = "INSERT INTO PlayerBans (Identifier, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4)" + database.GetSqlType() switch
|
||||
{
|
||||
query += "SELECT LAST_INSERT_ID();";
|
||||
}
|
||||
else
|
||||
{
|
||||
query += "SELECT CAST(last_insert_rowid() as INT);";
|
||||
}
|
||||
SqlType.Mysql => /*lang=mysql*/"; SELECT LAST_INSERT_ID();",
|
||||
SqlType.Sqlite => /*lang=sqlite*/"; SELECT last_insert_rowid();",
|
||||
SqlType.Postgres => /*lang=postgresql*/"RETURNING \"Identifier\";",
|
||||
_ => null
|
||||
};
|
||||
|
||||
int ticketId = database.QueryScalar<int>(query, args.Identifier, args.Reason, args.BanningUser, args.BanDateTime.Ticks, args.ExpirationDateTime.Ticks);
|
||||
|
||||
|
|
@ -361,8 +355,8 @@ namespace TShockAPI.DB
|
|||
return Bans[id];
|
||||
}
|
||||
|
||||
using (var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE TicketNumber=@0", id))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE TicketNumber=@0", id);
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
var ticketNumber = reader.Get<int>("TicketNumber");
|
||||
|
|
@ -374,7 +368,6 @@ namespace TShockAPI.DB
|
|||
|
||||
return new Ban(ticketNumber, identifier, reason, banningUser, date, expiration);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -393,8 +386,8 @@ namespace TShockAPI.DB
|
|||
query += $" AND Expiration > {DateTime.UtcNow.Ticks}";
|
||||
}
|
||||
|
||||
using (var reader = database.QueryReader(query, identifier))
|
||||
{
|
||||
using var reader = database.QueryReader(query, identifier);
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var ticketNumber = reader.Get<int>("TicketNumber");
|
||||
|
|
@ -407,7 +400,6 @@ namespace TShockAPI.DB
|
|||
yield return new Ban(ticketNumber, ident, reason, banningUser, date, expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an enumerable of bans for a given set of identifiers
|
||||
|
|
@ -418,16 +410,17 @@ namespace TShockAPI.DB
|
|||
public IEnumerable<Ban> GetBansByIdentifiers(bool currentOnly = true, params string[] identifiers)
|
||||
{
|
||||
//Generate a sequence of '@0, @1, @2, ... etc'
|
||||
var parameters = string.Join(", ", Enumerable.Range(0, identifiers.Count()).Select(p => $"@{p}"));
|
||||
var parameters = string.Join(", ", Enumerable.Range(0, identifiers.Length).Select(p => $"@{p}"));
|
||||
|
||||
string query = $"SELECT * FROM PlayerBans WHERE Identifier IN ({parameters})";
|
||||
|
||||
if (currentOnly)
|
||||
{
|
||||
query += $" AND Expiration > {DateTime.UtcNow.Ticks}";
|
||||
}
|
||||
|
||||
using (var reader = database.QueryReader(query, identifiers))
|
||||
{
|
||||
using var reader = database.QueryReader(query, identifiers);
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var ticketNumber = reader.Get<int>("TicketNumber");
|
||||
|
|
@ -440,7 +433,6 @@ namespace TShockAPI.DB
|
|||
yield return new Ban(ticketNumber, identifier, reason, banningUser, date, expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of bans from the database, sorted by their addition date from newest to oldest
|
||||
|
|
@ -457,9 +449,8 @@ namespace TShockAPI.DB
|
|||
List<Ban> banlist = new List<Ban>();
|
||||
try
|
||||
{
|
||||
var orderBy = SortToOrderByMap[sortMethod];
|
||||
using (var reader = database.QueryReader($"SELECT * FROM PlayerBans ORDER BY {orderBy}"))
|
||||
{
|
||||
using var reader = database.QueryReader($"SELECT * FROM PlayerBans ORDER BY {SortToOrderByMap[sortMethod]}");
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var ticketNumber = reader.Get<int>("TicketNumber");
|
||||
|
|
@ -473,7 +464,6 @@ namespace TShockAPI.DB
|
|||
banlist.Add(ban);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
@ -500,7 +490,7 @@ namespace TShockAPI.DB
|
|||
return false;
|
||||
}
|
||||
|
||||
internal Dictionary<BanSortMethod, string> SortToOrderByMap = new Dictionary<BanSortMethod, string>
|
||||
private readonly Dictionary<BanSortMethod, string> SortToOrderByMap = new()
|
||||
{
|
||||
{ BanSortMethod.AddedNewestToOldest, "Date DESC" },
|
||||
{ BanSortMethod.AddedOldestToNewest, "Date ASC" },
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Terraria;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -70,10 +71,8 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("unlockedSuperCart", MySqlDbType.Int32),
|
||||
new SqlColumn("enabledSuperCart", MySqlDbType.Int32)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
}
|
||||
|
||||
|
|
@ -83,8 +82,7 @@ namespace TShockAPI.DB
|
|||
|
||||
try
|
||||
{
|
||||
using (var reader = database.QueryReader("SELECT * FROM tsCharacter WHERE Account=@0", acctid))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM tsCharacter WHERE Account=@0", acctid);
|
||||
if (reader.Read())
|
||||
{
|
||||
playerData.exists = true;
|
||||
|
|
@ -137,7 +135,6 @@ namespace TShockAPI.DB
|
|||
return playerData;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
@ -154,7 +151,7 @@ namespace TShockAPI.DB
|
|||
if (items.Count < NetItem.MaxInventory)
|
||||
items.AddRange(new NetItem[NetItem.MaxInventory - items.Count]);
|
||||
|
||||
string initialItems = String.Join("~", items.Take(NetItem.MaxInventory));
|
||||
string initialItems = string.Join("~", items.Take(NetItem.MaxInventory));
|
||||
try
|
||||
{
|
||||
database.Query("INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, spawnX, spawnY, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8);",
|
||||
|
|
@ -204,7 +201,7 @@ namespace TShockAPI.DB
|
|||
{
|
||||
database.Query(
|
||||
"INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY, skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, hideVisuals, skinColor, eyeColor, questsCompleted, usingBiomeTorches, happyFunTorchTime, unlockedBiomeTorches, currentLoadoutIndex,ateArtisanBread, usedAegisCrystal, usedAegisFruit, usedArcaneCrystal, usedGalaxyPearl, usedGummyWorm, usedAmbrosia, unlockedSuperCart, enabledSuperCart) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20, @21, @22, @23, @24, @25, @26, @27, @28, @29, @30, @31, @32, @33);",
|
||||
player.Account.ID, playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), playerData.extraSlot, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.skinVariant, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor),TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisibleAccessory), TShock.Utils.EncodeColor(player.TPlayer.skinColor),TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0);
|
||||
player.Account.ID, playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, string.Join("~", playerData.inventory), playerData.extraSlot, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.skinVariant, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor),TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisibleAccessory), TShock.Utils.EncodeColor(player.TPlayer.skinColor),TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -218,7 +215,7 @@ namespace TShockAPI.DB
|
|||
{
|
||||
database.Query(
|
||||
"UPDATE tsCharacter SET Health = @0, MaxHealth = @1, Mana = @2, MaxMana = @3, Inventory = @4, spawnX = @6, spawnY = @7, hair = @8, hairDye = @9, hairColor = @10, pantsColor = @11, shirtColor = @12, underShirtColor = @13, shoeColor = @14, hideVisuals = @15, skinColor = @16, eyeColor = @17, questsCompleted = @18, skinVariant = @19, extraSlot = @20, usingBiomeTorches = @21, happyFunTorchTime = @22, unlockedBiomeTorches = @23, currentLoadoutIndex = @24, ateArtisanBread = @25, usedAegisCrystal = @26, usedAegisFruit = @27, usedArcaneCrystal = @28, usedGalaxyPearl = @29, usedGummyWorm = @30, usedAmbrosia = @31, unlockedSuperCart = @32, enabledSuperCart = @33 WHERE Account = @5;",
|
||||
playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), player.Account.ID, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor), TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisibleAccessory), TShock.Utils.EncodeColor(player.TPlayer.skinColor), TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished, player.TPlayer.skinVariant, player.TPlayer.extraAccessory ? 1 : 0, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0);
|
||||
playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, string.Join("~", playerData.inventory), player.Account.ID, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor), TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisibleAccessory), TShock.Utils.EncodeColor(player.TPlayer.skinColor), TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished, player.TPlayer.skinVariant, player.TPlayer.extraAccessory ? 1 : 0, player.TPlayer.UsingBiomeTorches ? 1 : 0, player.TPlayer.happyFunTorchTime ? 1 : 0, player.TPlayer.unlockedBiomeTorches ? 1 : 0, player.TPlayer.CurrentLoadoutIndex, player.TPlayer.ateArtisanBread ? 1 : 0, player.TPlayer.usedAegisCrystal ? 1 : 0, player.TPlayer.usedAegisFruit ? 1 : 0, player.TPlayer.usedArcaneCrystal ? 1 : 0, player.TPlayer.usedGalaxyPearl ? 1 : 0, player.TPlayer.usedGummyWorm ? 1 : 0, player.TPlayer.usedAmbrosia ? 1 : 0, player.TPlayer.unlockedSuperCart ? 1 : 0, player.TPlayer.enabledSuperCart ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -279,7 +276,7 @@ namespace TShockAPI.DB
|
|||
playerData.maxHealth,
|
||||
playerData.mana,
|
||||
playerData.maxMana,
|
||||
String.Join("~", playerData.inventory),
|
||||
string.Join("~", playerData.inventory),
|
||||
playerData.extraSlot,
|
||||
playerData.spawnX,
|
||||
playerData.spawnX,
|
||||
|
|
@ -325,7 +322,7 @@ namespace TShockAPI.DB
|
|||
playerData.maxHealth,
|
||||
playerData.mana,
|
||||
playerData.maxMana,
|
||||
String.Join("~", playerData.inventory),
|
||||
string.Join("~", playerData.inventory),
|
||||
player.Account.ID,
|
||||
playerData.spawnX,
|
||||
playerData.spawnX,
|
||||
|
|
|
|||
115
TShockAPI/DB/DbBuilder.cs
Normal file
115
TShockAPI/DB/DbBuilder.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Npgsql;
|
||||
using TerrariaApi.Server;
|
||||
using TShockAPI.Configuration;
|
||||
|
||||
namespace TShockAPI.DB;
|
||||
|
||||
/// <summary>
|
||||
/// Provides logic to build a DB connection.
|
||||
/// </summary>
|
||||
public sealed class DbBuilder
|
||||
{
|
||||
private readonly TShock _caller;
|
||||
private readonly TShockConfig _config;
|
||||
private readonly string _savePath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DbBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="caller">The TShock instance calling this DbBuilder.</param>
|
||||
/// <param name="config">The TShock configuration, supplied by <see cref="TShock.Config" /> at init.</param>
|
||||
/// <param name="savePath">The savePath registered by TShock. See <see cref="TShock.SavePath" />.</param>
|
||||
public DbBuilder(TShock caller, TShockConfig config, string savePath)
|
||||
{
|
||||
_caller = caller;
|
||||
_config = config;
|
||||
_savePath = savePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a DB connection based on the provided configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">The TShock configuration.</param>
|
||||
/// <remarks>
|
||||
/// Default settings will result in a local sqlite database file named "tshock.db" in the current directory to be used as server DB.
|
||||
/// </remarks>
|
||||
public IDbConnection BuildDbConnection()
|
||||
{
|
||||
string dbType = _config.Settings.StorageType.ToLowerInvariant();
|
||||
|
||||
return dbType switch
|
||||
{
|
||||
"sqlite" => BuildSqliteConnection(),
|
||||
"mysql" => BuildMySqlConnection(),
|
||||
"postgres" => BuildPostgresConnection(),
|
||||
_ => throw new("Invalid storage type")
|
||||
};
|
||||
}
|
||||
|
||||
private SqliteConnection BuildSqliteConnection()
|
||||
{
|
||||
string dbFilePath = Path.Combine(_savePath, _config.Settings.SqliteDBPath);
|
||||
|
||||
if (Path.GetDirectoryName(dbFilePath) is not { } dbDirPath)
|
||||
{
|
||||
throw new DirectoryNotFoundException($"The SQLite database path '{dbFilePath}' could not be found.");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(dbDirPath);
|
||||
|
||||
return new($"Data Source={dbFilePath}");
|
||||
}
|
||||
|
||||
private MySqlConnection BuildMySqlConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] hostport = _config.Settings.MySqlHost.Split(':');
|
||||
|
||||
MySqlConnectionStringBuilder connStrBuilder = new()
|
||||
{
|
||||
Server = hostport[0],
|
||||
Port = hostport.Length > 1 ? uint.Parse(hostport[1]) : 3306,
|
||||
Database = _config.Settings.MySqlDbName,
|
||||
UserID = _config.Settings.MySqlUsername,
|
||||
Password = _config.Settings.MySqlPassword
|
||||
};
|
||||
|
||||
return new(connStrBuilder.ToString());
|
||||
}
|
||||
catch (MySqlException e)
|
||||
{
|
||||
ServerApi.LogWriter.PluginWriteLine(_caller, e.ToString(), TraceLevel.Error);
|
||||
throw new("MySql not setup correctly", e);
|
||||
}
|
||||
}
|
||||
|
||||
private NpgsqlConnection BuildPostgresConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] hostport = _config.Settings.PostgresHost.Split(':');
|
||||
|
||||
NpgsqlConnectionStringBuilder connStrBuilder = new()
|
||||
{
|
||||
Host = hostport[0],
|
||||
Port = hostport.Length > 1 ? int.Parse(hostport[1]) : 5432,
|
||||
Database = _config.Settings.PostgresDbName,
|
||||
Username = _config.Settings.PostgresUsername,
|
||||
Password = _config.Settings.PostgresPassword
|
||||
};
|
||||
|
||||
return new(connStrBuilder.ToString());
|
||||
}
|
||||
catch (NpgsqlException e)
|
||||
{
|
||||
ServerApi.LogWriter.PluginWriteLine(_caller, e.ToString(), TraceLevel.Error);
|
||||
throw new("Postgres not setup correctly", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ using System.Data;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -50,10 +51,9 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("Prefix", MySqlDbType.Text),
|
||||
new SqlColumn("Suffix", MySqlDbType.Text)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder)new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
|
||||
if (creator.EnsureTableStructure(table))
|
||||
{
|
||||
// Add default groups if they don't exist
|
||||
|
|
@ -74,6 +74,7 @@ namespace TShockAPI.DB
|
|||
Permissions.canchangepassword,
|
||||
Permissions.canlogout,
|
||||
Permissions.summonboss,
|
||||
Permissions.spawnpets,
|
||||
Permissions.worldupgrades,
|
||||
Permissions.whisper,
|
||||
Permissions.wormhole,
|
||||
|
|
@ -293,7 +294,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="parentname">parent of group</param>
|
||||
/// <param name="permissions">permissions</param>
|
||||
/// <param name="chatcolor">chatcolor</param>
|
||||
public void AddGroup(String name, string parentname, String permissions, String chatcolor)
|
||||
public void AddGroup(string name, string parentname, string permissions, string chatcolor)
|
||||
{
|
||||
if (GroupExists(name))
|
||||
{
|
||||
|
|
@ -314,16 +315,23 @@ namespace TShockAPI.DB
|
|||
group.Parent = parent;
|
||||
}
|
||||
|
||||
string query = (TShock.Config.Settings.StorageType.ToLower() == "sqlite")
|
||||
? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);"
|
||||
: "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3";
|
||||
if (database.Query(query, name, parentname, permissions, chatcolor) == 1)
|
||||
string query = database.GetSqlType() switch
|
||||
{
|
||||
SqlType.Sqlite => "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);",
|
||||
SqlType.Mysql => "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3",
|
||||
SqlType.Postgres => "INSERT INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3) ON CONFLICT (GroupName) DO NOTHING",
|
||||
_ => throw new NotSupportedException(GetString("Unsupported database type."))
|
||||
};
|
||||
|
||||
if (database.Query(query, name, parentname, permissions, chatcolor) is 1)
|
||||
{
|
||||
groups.Add(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new GroupManagerException(GetString($"Failed to add group {name}."));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a group including permissions
|
||||
|
|
@ -382,7 +390,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="name">The group's name.</param>
|
||||
/// <param name="newName">The new name.</param>
|
||||
/// <returns>The result from the operation to be sent back to the user.</returns>
|
||||
public String RenameGroup(string name, string newName)
|
||||
public string RenameGroup(string name, string newName)
|
||||
{
|
||||
if (!GroupExists(name))
|
||||
{
|
||||
|
|
@ -394,11 +402,9 @@ namespace TShockAPI.DB
|
|||
throw new GroupExistsException(newName);
|
||||
}
|
||||
|
||||
using (var db = database.CloneEx())
|
||||
{
|
||||
using var db = database.CloneEx();
|
||||
db.Open();
|
||||
using (var transaction = db.BeginTransaction())
|
||||
{
|
||||
using var transaction = db.BeginTransaction();
|
||||
try
|
||||
{
|
||||
using (var command = db.CreateCommand())
|
||||
|
|
@ -475,8 +481,6 @@ namespace TShockAPI.DB
|
|||
TShock.Log.Error(GetString($"An exception has occurred during database rollback: {rollbackEx.Message}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new GroupManagerException(GetString($"Failed to rename group {name}."));
|
||||
}
|
||||
|
|
@ -487,7 +491,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="name">The group's name.</param>
|
||||
/// <param name="exceptions">Whether exceptions will be thrown in case something goes wrong.</param>
|
||||
/// <returns>The result from the operation to be sent back to the user.</returns>
|
||||
public String DeleteGroup(String name, bool exceptions = false)
|
||||
public string DeleteGroup(string name, bool exceptions = false)
|
||||
{
|
||||
if (!GroupExists(name))
|
||||
{
|
||||
|
|
@ -520,7 +524,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="name">The group name.</param>
|
||||
/// <param name="permissions">The permission list.</param>
|
||||
/// <returns>The result from the operation to be sent back to the user.</returns>
|
||||
public String AddPermissions(String name, List<String> permissions)
|
||||
public string AddPermissions(string name, List<string> permissions)
|
||||
{
|
||||
if (!GroupExists(name))
|
||||
return GetString($"Group {name} doesn't exist.");
|
||||
|
|
@ -543,7 +547,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="name">The group name.</param>
|
||||
/// <param name="permissions">The permission list.</param>
|
||||
/// <returns>The result from the operation to be sent back to the user.</returns>
|
||||
public String DeletePermissions(String name, List<String> permissions)
|
||||
public string DeletePermissions(string name, List<string> permissions)
|
||||
{
|
||||
if (!GroupExists(name))
|
||||
return GetString($"Group {name} doesn't exist.");
|
||||
|
|
|
|||
|
|
@ -1,401 +0,0 @@
|
|||
/*
|
||||
TShock, a server mod for Terraria
|
||||
Copyright (C) 2011-2019 Pryaxis & TShock Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using MySql.Data.MySqlClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using TShockAPI.Extensions;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for various SQL related utilities.
|
||||
/// </summary>
|
||||
public interface IQueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a table from a SqlTable object.
|
||||
/// </summary>
|
||||
/// <param name="table">The SqlTable to create the table from</param>
|
||||
/// <returns>The sql query for the table creation.</returns>
|
||||
string CreateTable(SqlTable table);
|
||||
|
||||
/// <summary>
|
||||
/// Alter a table from source to destination
|
||||
/// </summary>
|
||||
/// <param name="from">Must have name and column names. Column types are not required</param>
|
||||
/// <param name="to">Must have column names and column types.</param>
|
||||
/// <returns>The SQL Query</returns>
|
||||
string AlterTable(SqlTable from, SqlTable to);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the MySqlDbType enum to it's string representation.
|
||||
/// </summary>
|
||||
/// <param name="type">The MySqlDbType type</param>
|
||||
/// <param name="length">The length of the datatype</param>
|
||||
/// <returns>The string representation</returns>
|
||||
string DbTypeToString(MySqlDbType type, int? length);
|
||||
|
||||
/// <summary>
|
||||
/// A UPDATE Query
|
||||
/// </summary>
|
||||
/// <param name="table">The table to update</param>
|
||||
/// <param name="values">The values to change</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres);
|
||||
|
||||
/// <summary>
|
||||
/// A INSERT query
|
||||
/// </summary>
|
||||
/// <param name="table">The table to insert to</param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns>The SQL Query</returns>
|
||||
string InsertValues(string table, List<SqlValue> values);
|
||||
|
||||
/// <summary>
|
||||
/// A SELECT query to get all columns
|
||||
/// </summary>
|
||||
/// <param name="table">The table to select from</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
string ReadColumn(string table, List<SqlValue> wheres);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes row(s).
|
||||
/// </summary>
|
||||
/// <param name="table">The table to delete the row from</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
string DeleteRow(string table, List<SqlValue> wheres);
|
||||
|
||||
/// <summary>
|
||||
/// Renames the given table.
|
||||
/// </summary>
|
||||
/// <param name="from">Old name of the table</param>
|
||||
/// <param name="to">New name of the table</param>
|
||||
/// <returns>The sql query for renaming the table.</returns>
|
||||
string RenameTable(string from, string to);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query Creator for Sqlite
|
||||
/// </summary>
|
||||
public class SqliteQueryCreator : GenericQueryCreator, IQueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a table from a SqlTable object.
|
||||
/// </summary>
|
||||
/// <param name="table">The SqlTable to create the table from</param>
|
||||
/// <returns>The sql query for the table creation.</returns>
|
||||
public override string CreateTable(SqlTable table)
|
||||
{
|
||||
ValidateSqlColumnType(table.Columns);
|
||||
var columns =
|
||||
table.Columns.Select(
|
||||
c =>
|
||||
"'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name,
|
||||
DbTypeToString(c.Type, c.Length),
|
||||
c.Primary ? "PRIMARY KEY" : "",
|
||||
c.AutoIncrement ? "AUTOINCREMENT" : "",
|
||||
c.NotNull ? "NOT NULL" : "",
|
||||
c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : ""));
|
||||
var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name);
|
||||
return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name),
|
||||
string.Join(", ", columns),
|
||||
uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames the given table.
|
||||
/// </summary>
|
||||
/// <param name="from">Old name of the table</param>
|
||||
/// <param name="to">New name of the table</param>
|
||||
/// <returns>The sql query for renaming the table.</returns>
|
||||
public override string RenameTable(string from, string to)
|
||||
{
|
||||
return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string>
|
||||
{
|
||||
{ MySqlDbType.VarChar, "TEXT" },
|
||||
{ MySqlDbType.String, "TEXT" },
|
||||
{ MySqlDbType.Text, "TEXT" },
|
||||
{ MySqlDbType.TinyText, "TEXT" },
|
||||
{ MySqlDbType.MediumText, "TEXT" },
|
||||
{ MySqlDbType.LongText, "TEXT" },
|
||||
{ MySqlDbType.Float, "REAL" },
|
||||
{ MySqlDbType.Double, "REAL" },
|
||||
{ MySqlDbType.Int32, "INTEGER" },
|
||||
{ MySqlDbType.Blob, "BLOB" },
|
||||
{ MySqlDbType.Int64, "BIGINT"},
|
||||
{ MySqlDbType.DateTime, "DATETIME"},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts the MySqlDbType enum to it's string representation.
|
||||
/// </summary>
|
||||
/// <param name="type">The MySqlDbType type</param>
|
||||
/// <param name="length">The length of the datatype</param>
|
||||
/// <returns>The string representation</returns>
|
||||
public string DbTypeToString(MySqlDbType type, int? length)
|
||||
{
|
||||
string ret;
|
||||
if (TypesAsStrings.TryGetValue(type, out ret))
|
||||
return ret;
|
||||
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes the table name
|
||||
/// </summary>
|
||||
/// <param name="table">The name of the table to be escaped</param>
|
||||
/// <returns></returns>
|
||||
protected override string EscapeTableName(string table)
|
||||
{
|
||||
return $"\'{table}\'";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query Creator for MySQL
|
||||
/// </summary>
|
||||
public class MysqlQueryCreator : GenericQueryCreator, IQueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a table from a SqlTable object.
|
||||
/// </summary>
|
||||
/// <param name="table">The SqlTable to create the table from</param>
|
||||
/// <returns>The sql query for the table creation.</returns>
|
||||
public override string CreateTable(SqlTable table)
|
||||
{
|
||||
ValidateSqlColumnType(table.Columns);
|
||||
var columns =
|
||||
table.Columns.Select(
|
||||
c =>
|
||||
"`{0}` {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length),
|
||||
c.Primary ? "PRIMARY KEY" : "",
|
||||
c.AutoIncrement ? "AUTO_INCREMENT" : "",
|
||||
c.NotNull ? "NOT NULL" : "",
|
||||
c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : ""));
|
||||
var uniques = table.Columns.Where(c => c.Unique).Select(c => $"`{c.Name}`");
|
||||
return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns),
|
||||
uniques.Count() > 0
|
||||
? ", UNIQUE({0})".SFormat(string.Join(", ", uniques))
|
||||
: "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames the given table.
|
||||
/// </summary>
|
||||
/// <param name="from">Old name of the table</param>
|
||||
/// <param name="to">New name of the table</param>
|
||||
/// <returns>The sql query for renaming the table.</returns>
|
||||
public override string RenameTable(string from, string to)
|
||||
{
|
||||
return "RENAME TABLE {0} TO {1}".SFormat(from, to);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string>
|
||||
{
|
||||
{ MySqlDbType.VarChar, "VARCHAR" },
|
||||
{ MySqlDbType.String, "CHAR" },
|
||||
{ MySqlDbType.Text, "TEXT" },
|
||||
{ MySqlDbType.TinyText, "TINYTEXT" },
|
||||
{ MySqlDbType.MediumText, "MEDIUMTEXT" },
|
||||
{ MySqlDbType.LongText, "LONGTEXT" },
|
||||
{ MySqlDbType.Float, "FLOAT" },
|
||||
{ MySqlDbType.Double, "DOUBLE" },
|
||||
{ MySqlDbType.Int32, "INT" },
|
||||
{ MySqlDbType.Int64, "BIGINT"},
|
||||
{ MySqlDbType.DateTime, "DATETIME"},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts the MySqlDbType enum to it's string representation.
|
||||
/// </summary>
|
||||
/// <param name="type">The MySqlDbType type</param>
|
||||
/// <param name="length">The length of the datatype</param>
|
||||
/// <returns>The string representation</returns>
|
||||
public string DbTypeToString(MySqlDbType type, int? length)
|
||||
{
|
||||
string ret;
|
||||
if (TypesAsStrings.TryGetValue(type, out ret))
|
||||
return ret + (length != null ? "({0})".SFormat((int)length) : "");
|
||||
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes the table name
|
||||
/// </summary>
|
||||
/// <param name="table">The name of the table to be escaped</param>
|
||||
/// <returns></returns>
|
||||
protected override string EscapeTableName(string table)
|
||||
{
|
||||
return table.SFormat("`{0}`", table);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Generic Query Creator (abstract)
|
||||
/// </summary>
|
||||
public abstract class GenericQueryCreator
|
||||
{
|
||||
protected static Random rand = new Random();
|
||||
|
||||
/// <summary>
|
||||
/// Escapes the table name
|
||||
/// </summary>
|
||||
/// <param name="table">The name of the table to be escaped</param>
|
||||
/// <returns></returns>
|
||||
protected abstract string EscapeTableName(string table);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a table from a SqlTable object.
|
||||
/// </summary>
|
||||
/// <param name="table">The SqlTable to create the table from</param>
|
||||
/// <returns>The sql query for the table creation.</returns>
|
||||
public abstract string CreateTable(SqlTable table);
|
||||
|
||||
/// <summary>
|
||||
/// Renames the given table.
|
||||
/// </summary>
|
||||
/// <param name="from">Old name of the table</param>
|
||||
/// <param name="to">New name of the table</param>
|
||||
/// <returns>The sql query for renaming the table.</returns>
|
||||
public abstract string RenameTable(string from, string to);
|
||||
|
||||
/// <summary>
|
||||
/// Alter a table from source to destination
|
||||
/// </summary>
|
||||
/// <param name="from">Must have name and column names. Column types are not required</param>
|
||||
/// <param name="to">Must have column names and column types.</param>
|
||||
/// <returns>The SQL Query</returns>
|
||||
public string AlterTable(SqlTable from, SqlTable to)
|
||||
{
|
||||
var rstr = rand.NextString(20);
|
||||
var escapedTable = EscapeTableName(from.Name);
|
||||
var tmpTable = EscapeTableName("{0}_{1}".SFormat(rstr, from.Name));
|
||||
var alter = RenameTable(escapedTable, tmpTable);
|
||||
var create = CreateTable(to);
|
||||
// combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable.
|
||||
// exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data?
|
||||
var columns = string.Join(", ", from.Columns.Where(c => to.Columns.Any(c2 => c2.Name == c.Name)).Select(c => $"`{c.Name}`"));
|
||||
var insert = "INSERT INTO {0} ({1}) SELECT {1} FROM {2}".SFormat(escapedTable, columns, tmpTable);
|
||||
var drop = "DROP TABLE {0}".SFormat(tmpTable);
|
||||
return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for errors in the columns.
|
||||
/// </summary>
|
||||
/// <param name="columns"></param>
|
||||
/// <exception cref="SqlColumnException"></exception>
|
||||
public void ValidateSqlColumnType(List<SqlColumn> columns)
|
||||
{
|
||||
columns.ForEach(x =>
|
||||
{
|
||||
if (x.DefaultCurrentTimestamp && x.Type != MySqlDbType.DateTime)
|
||||
{
|
||||
throw new SqlColumnException(GetString("Can't set to true SqlColumn.DefaultCurrentTimestamp when the MySqlDbType is not DateTime"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes row(s).
|
||||
/// </summary>
|
||||
/// <param name="table">The table to delete the row from</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
public string DeleteRow(string table, List<SqlValue> wheres)
|
||||
{
|
||||
return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A UPDATE Query
|
||||
/// </summary>
|
||||
/// <param name="table">The table to update</param>
|
||||
/// <param name="values">The values to change</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
public string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres)
|
||||
{
|
||||
if (0 == values.Count)
|
||||
throw new ArgumentException(GetString("No values supplied"));
|
||||
|
||||
return "UPDATE {0} SET {1} {2}".SFormat(EscapeTableName(table), string.Join(", ", values.Select(v => v.Name + " = " + v.Value)), BuildWhere(wheres));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A SELECT query to get all columns
|
||||
/// </summary>
|
||||
/// <param name="table">The table to select from</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
public string ReadColumn(string table, List<SqlValue> wheres)
|
||||
{
|
||||
return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A INSERT query
|
||||
/// </summary>
|
||||
/// <param name="table">The table to insert to</param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns>The SQL Query</returns>
|
||||
public string InsertValues(string table, List<SqlValue> values)
|
||||
{
|
||||
var sbnames = new StringBuilder();
|
||||
var sbvalues = new StringBuilder();
|
||||
int count = 0;
|
||||
foreach (SqlValue value in values)
|
||||
{
|
||||
sbnames.Append(value.Name);
|
||||
sbvalues.Append(value.Value.ToString());
|
||||
|
||||
if (count != values.Count - 1)
|
||||
{
|
||||
sbnames.Append(", ");
|
||||
sbvalues.Append(", ");
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the SQL WHERE clause
|
||||
/// </summary>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns></returns>
|
||||
protected static string BuildWhere(List<SqlValue> wheres)
|
||||
{
|
||||
if (0 == wheres.Count)
|
||||
return string.Empty;
|
||||
|
||||
return "WHERE {0}".SFormat(string.Join(", ", wheres.Select(v => $"{v.Name}" + " = " + v.Value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ using System.Collections.Generic;
|
|||
using System.Data;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
using TShockAPI.DB.Queries;
|
||||
using TShockAPI.Hooks;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
|
|
@ -38,10 +39,9 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("ItemName", MySqlDbType.VarChar, 50) {Primary = true},
|
||||
new SqlColumn("AllowedGroups", MySqlDbType.Text)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
|
||||
creator.EnsureTableStructure(table);
|
||||
UpdateItemBans();
|
||||
}
|
||||
|
|
@ -50,8 +50,8 @@ namespace TShockAPI.DB
|
|||
{
|
||||
ItemBans.Clear();
|
||||
|
||||
using (var reader = database.QueryReader("SELECT * FROM ItemBans"))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM ItemBans");
|
||||
|
||||
while (reader != null && reader.Read())
|
||||
{
|
||||
ItemBan ban = new ItemBan(reader.Get<string>("ItemName"));
|
||||
|
|
@ -59,7 +59,6 @@ namespace TShockAPI.DB
|
|||
ItemBans.Add(ban);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewBan(string itemname = "")
|
||||
{
|
||||
|
|
@ -91,14 +90,7 @@ namespace TShockAPI.DB
|
|||
}
|
||||
}
|
||||
|
||||
public bool ItemIsBanned(string name)
|
||||
{
|
||||
if (ItemBans.Contains(new ItemBan(name)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool ItemIsBanned(string name) => ItemBans.Contains(new(name));
|
||||
|
||||
public bool ItemIsBanned(string name, TSPlayer ply)
|
||||
{
|
||||
|
|
@ -108,13 +100,12 @@ namespace TShockAPI.DB
|
|||
|
||||
public bool AllowGroup(string item, string name)
|
||||
{
|
||||
string groupsNew = "";
|
||||
ItemBan b = GetItemBanByName(item);
|
||||
if (b != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
groupsNew = String.Join(",", b.AllowedGroups);
|
||||
string groupsNew = string.Join(",", b.AllowedGroups);
|
||||
if (groupsNew.Length > 0)
|
||||
groupsNew += ",";
|
||||
groupsNew += name;
|
||||
|
|
@ -157,7 +148,7 @@ namespace TShockAPI.DB
|
|||
return false;
|
||||
}
|
||||
|
||||
public ItemBan GetItemBanByName(String name)
|
||||
public ItemBan GetItemBanByName(string name)
|
||||
{
|
||||
for (int i = 0; i < ItemBans.Count; i++)
|
||||
{
|
||||
|
|
@ -188,10 +179,7 @@ namespace TShockAPI.DB
|
|||
AllowedGroups = new List<string>();
|
||||
}
|
||||
|
||||
public bool Equals(ItemBan other)
|
||||
{
|
||||
return Name == other.Name;
|
||||
}
|
||||
public bool Equals(ItemBan other) => Name == other.Name;
|
||||
|
||||
public bool HasPermissionToUseItem(TSPlayer ply)
|
||||
{
|
||||
|
|
@ -224,12 +212,12 @@ namespace TShockAPI.DB
|
|||
// could add in the other permissions in this class instead of a giant if switch.
|
||||
}
|
||||
|
||||
public void SetAllowedGroups(String groups)
|
||||
public void SetAllowedGroups(string groups)
|
||||
{
|
||||
// prevent null pointer exceptions
|
||||
if (!string.IsNullOrEmpty(groups))
|
||||
{
|
||||
List<String> groupArr = groups.Split(',').ToList();
|
||||
List<string> groupArr = groups.Split(',').ToList();
|
||||
|
||||
for (int i = 0; i < groupArr.Count; i++)
|
||||
{
|
||||
|
|
@ -247,7 +235,7 @@ namespace TShockAPI.DB
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name + (AllowedGroups.Count > 0 ? " (" + String.Join(",", AllowedGroups) + ")" : "");
|
||||
return Name + (AllowedGroups.Count > 0 ? " (" + string.Join(",", AllowedGroups) + ")" : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ using System.Collections.Generic;
|
|||
using System.Data;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
using TShockAPI.DB.Queries;
|
||||
using TShockAPI.Hooks;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
|
|
@ -38,10 +39,8 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("ProjectileID", MySqlDbType.Int32) {Primary = true},
|
||||
new SqlColumn("AllowedGroups", MySqlDbType.Text)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
UpdateBans();
|
||||
}
|
||||
|
|
@ -50,16 +49,15 @@ namespace TShockAPI.DB
|
|||
{
|
||||
ProjectileBans.Clear();
|
||||
|
||||
using (var reader = database.QueryReader("SELECT * FROM ProjectileBans"))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM ProjectileBans");
|
||||
|
||||
while (reader != null && reader.Read())
|
||||
{
|
||||
ProjectileBan ban = new ProjectileBan((short) reader.Get<Int32>("ProjectileID"));
|
||||
ProjectileBan ban = new ProjectileBan((short) reader.Get<int>("ProjectileID"));
|
||||
ban.SetAllowedGroups(reader.Get<string>("AllowedGroups"));
|
||||
ProjectileBans.Add(ban);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewBan(short id = 0)
|
||||
{
|
||||
|
|
@ -92,14 +90,7 @@ namespace TShockAPI.DB
|
|||
}
|
||||
}
|
||||
|
||||
public bool ProjectileIsBanned(short id)
|
||||
{
|
||||
if (ProjectileBans.Contains(new ProjectileBan(id)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool ProjectileIsBanned(short id) => ProjectileBans.Contains(new(id));
|
||||
|
||||
public bool ProjectileIsBanned(short id, TSPlayer ply)
|
||||
{
|
||||
|
|
@ -113,13 +104,12 @@ namespace TShockAPI.DB
|
|||
|
||||
public bool AllowGroup(short id, string name)
|
||||
{
|
||||
string groupsNew = "";
|
||||
ProjectileBan b = GetBanById(id);
|
||||
if (b != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
groupsNew = String.Join(",", b.AllowedGroups);
|
||||
string groupsNew = string.Join(",", b.AllowedGroups);
|
||||
if (groupsNew.Length > 0)
|
||||
groupsNew += ",";
|
||||
groupsNew += name;
|
||||
|
|
@ -193,10 +183,7 @@ namespace TShockAPI.DB
|
|||
AllowedGroups = new List<string>();
|
||||
}
|
||||
|
||||
public bool Equals(ProjectileBan other)
|
||||
{
|
||||
return ID == other.ID;
|
||||
}
|
||||
public bool Equals(ProjectileBan other) => ID == other.ID;
|
||||
|
||||
public bool HasPermissionToCreateProjectile(TSPlayer ply)
|
||||
{
|
||||
|
|
@ -229,12 +216,12 @@ namespace TShockAPI.DB
|
|||
// could add in the other permissions in this class instead of a giant if switch.
|
||||
}
|
||||
|
||||
public void SetAllowedGroups(String groups)
|
||||
public void SetAllowedGroups(string groups)
|
||||
{
|
||||
// prevent null pointer exceptions
|
||||
if (!string.IsNullOrEmpty(groups))
|
||||
{
|
||||
List<String> groupArr = groups.Split(',').ToList();
|
||||
List<string> groupArr = groups.Split(',').ToList();
|
||||
|
||||
for (int i = 0; i < groupArr.Count; i++)
|
||||
{
|
||||
|
|
@ -245,14 +232,8 @@ namespace TShockAPI.DB
|
|||
}
|
||||
}
|
||||
|
||||
public bool RemoveGroup(string groupName)
|
||||
{
|
||||
return AllowedGroups.Remove(groupName);
|
||||
}
|
||||
public bool RemoveGroup(string groupName) => AllowedGroups.Remove(groupName);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ID + (AllowedGroups.Count > 0 ? " (" + String.Join(",", AllowedGroups) + ")" : "");
|
||||
}
|
||||
public override string ToString() => ID + (AllowedGroups.Count > 0 ? $" ({string.Join(",", AllowedGroups)})" : "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
136
TShockAPI/DB/Queries/GenericQueryBuilder.cs
Normal file
136
TShockAPI/DB/Queries/GenericQueryBuilder.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
TShock, a server mod for Terraria
|
||||
Copyright (C) 2011-2025 Pryaxis & TShock Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MySql.Data.MySqlClient;
|
||||
using TShockAPI.Extensions;
|
||||
|
||||
namespace TShockAPI.DB.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// A Generic Query Creator (abstract)
|
||||
/// </summary>
|
||||
public abstract class GenericQueryBuilder : IQueryBuilder
|
||||
{
|
||||
protected static Random rand = new Random();
|
||||
|
||||
/// <summary>
|
||||
/// Escapes the table name
|
||||
/// </summary>
|
||||
/// <param name="table">The name of the table to be escaped</param>
|
||||
/// <returns></returns>
|
||||
protected abstract string EscapeTableName(string table);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string CreateTable(SqlTable table);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string RenameTable(string from, string to);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AlterTable(SqlTable from, SqlTable to)
|
||||
{
|
||||
var rstr = rand.NextString(20);
|
||||
var escapedTable = EscapeTableName(from.Name);
|
||||
var tmpTable = EscapeTableName("{0}_{1}".SFormat(rstr, from.Name));
|
||||
var alter = RenameTable(escapedTable, tmpTable);
|
||||
var create = CreateTable(to);
|
||||
// combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable.
|
||||
// exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data?
|
||||
var columns = string.Join(", ", from.Columns.Where(c => to.Columns.Any(c2 => c2.Name == c.Name)).Select(c => $"`{c.Name}`"));
|
||||
var insert = "INSERT INTO {0} ({1}) SELECT {1} FROM {2}".SFormat(escapedTable, columns, tmpTable);
|
||||
var drop = "DROP TABLE {0}".SFormat(tmpTable);
|
||||
return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string DbTypeToString(MySqlDbType type, int? length);
|
||||
|
||||
/// <summary>
|
||||
/// Check for errors in the columns.
|
||||
/// </summary>
|
||||
/// <param name="columns"></param>
|
||||
/// <exception cref="SqlColumnException"></exception>
|
||||
protected static void ValidateSqlColumnType(List<SqlColumn> columns)
|
||||
{
|
||||
columns.ForEach(x =>
|
||||
{
|
||||
if (x.DefaultCurrentTimestamp && x.Type != MySqlDbType.DateTime)
|
||||
{
|
||||
throw new SqlColumnException(GetString("Can't set to true SqlColumn.DefaultCurrentTimestamp when the MySqlDbType is not DateTime"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DeleteRow(string table, List<SqlValue> wheres)
|
||||
{
|
||||
return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres)
|
||||
{
|
||||
if (0 == values.Count)
|
||||
throw new ArgumentException(GetString("No values supplied"));
|
||||
|
||||
return "UPDATE {0} SET {1} {2}".SFormat(EscapeTableName(table), string.Join(", ", values.Select(v => v.Name + " = " + v.Value)), BuildWhere(wheres));
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ReadColumn(string table, List<SqlValue> wheres)
|
||||
{
|
||||
return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
|
||||
}
|
||||
|
||||
|
||||
public string InsertValues(string table, List<SqlValue> values)
|
||||
{
|
||||
var sbnames = new StringBuilder();
|
||||
var sbvalues = new StringBuilder();
|
||||
int count = 0;
|
||||
foreach (SqlValue value in values)
|
||||
{
|
||||
sbnames.Append(value.Name);
|
||||
sbvalues.Append(value.Value.ToString());
|
||||
|
||||
if (count != values.Count - 1)
|
||||
{
|
||||
sbnames.Append(", ");
|
||||
sbvalues.Append(", ");
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the SQL WHERE clause
|
||||
/// </summary>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns></returns>
|
||||
protected static string BuildWhere(List<SqlValue> wheres) => wheres.Count > 0
|
||||
? string.Empty
|
||||
: "WHERE {0}".SFormat(string.Join(", ", wheres.Select(v => $"{v.Name} = {v.Value}")));
|
||||
}
|
||||
92
TShockAPI/DB/Queries/IQueryBuilder.cs
Normal file
92
TShockAPI/DB/Queries/IQueryBuilder.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
TShock, a server mod for Terraria
|
||||
Copyright (C) 2011-2025 Pryaxis & TShock Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace TShockAPI.DB.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for various SQL related utilities.
|
||||
/// </summary>
|
||||
public interface IQueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a table from a SqlTable object.
|
||||
/// </summary>
|
||||
/// <param name="table">The SqlTable to create the table from</param>
|
||||
/// <returns>The sql query for the table creation.</returns>
|
||||
string CreateTable(SqlTable table);
|
||||
|
||||
/// <summary>
|
||||
/// Alter a table from source to destination
|
||||
/// </summary>
|
||||
/// <param name="from">Must have name and column names. Column types are not required</param>
|
||||
/// <param name="to">Must have column names and column types.</param>
|
||||
/// <returns>The SQL Query</returns>
|
||||
string AlterTable(SqlTable from, SqlTable to);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the MySqlDbType enum to it's string representation.
|
||||
/// </summary>
|
||||
/// <param name="type">The MySqlDbType type</param>
|
||||
/// <param name="length">The length of the datatype</param>
|
||||
/// <returns>The string representation</returns>
|
||||
string DbTypeToString(MySqlDbType type, int? length);
|
||||
|
||||
/// <summary>
|
||||
/// A UPDATE Query
|
||||
/// </summary>
|
||||
/// <param name="table">The table to update</param>
|
||||
/// <param name="values">The values to change</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres);
|
||||
|
||||
/// <summary>
|
||||
/// A INSERT query
|
||||
/// </summary>
|
||||
/// <param name="table">The table to insert to</param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns>The SQL Query</returns>
|
||||
string InsertValues(string table, List<SqlValue> values);
|
||||
|
||||
/// <summary>
|
||||
/// A SELECT query to get all columns
|
||||
/// </summary>
|
||||
/// <param name="table">The table to select from</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
string ReadColumn(string table, List<SqlValue> wheres);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes row(s).
|
||||
/// </summary>
|
||||
/// <param name="table">The table to delete the row from</param>
|
||||
/// <param name="wheres"></param>
|
||||
/// <returns>The SQL query</returns>
|
||||
string DeleteRow(string table, List<SqlValue> wheres);
|
||||
|
||||
/// <summary>
|
||||
/// Renames the given table.
|
||||
/// </summary>
|
||||
/// <param name="from">Old name of the table</param>
|
||||
/// <param name="to">New name of the table</param>
|
||||
/// <returns>The sql query for renaming the table.</returns>
|
||||
string RenameTable(string from, string to);
|
||||
}
|
||||
88
TShockAPI/DB/Queries/MysqlQueryBuilder.cs
Normal file
88
TShockAPI/DB/Queries/MysqlQueryBuilder.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
TShock, a server mod for Terraria
|
||||
Copyright (C) 2011-2025 Pryaxis & TShock Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace TShockAPI.DB.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Query Creator for MySQL
|
||||
/// </summary>
|
||||
public class MysqlQueryBuilder : GenericQueryBuilder, IQueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a table from a SqlTable object.
|
||||
/// </summary>
|
||||
/// <param name="table">The SqlTable to create the table from</param>
|
||||
/// <returns>The sql query for the table creation.</returns>
|
||||
public override string CreateTable(SqlTable table)
|
||||
{
|
||||
ValidateSqlColumnType(table.Columns);
|
||||
|
||||
var columns =
|
||||
table.Columns.Select(
|
||||
c =>
|
||||
"`{0}` {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length),
|
||||
c.Primary ? "PRIMARY KEY" : "",
|
||||
c.AutoIncrement ? "AUTO_INCREMENT" : "",
|
||||
c.NotNull ? "NOT NULL" : "",
|
||||
c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : ""));
|
||||
|
||||
var uniques = table.Columns.Where(c => c.Unique).Select(c => $"`{c.Name}`");
|
||||
return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns),
|
||||
uniques.Any()
|
||||
? ", UNIQUE({0})".SFormat(string.Join(", ", uniques))
|
||||
: "");
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string RenameTable(string from, string to) => /*lang=mysql*/"RENAME TABLE {0} TO {1}".SFormat(from, to);
|
||||
|
||||
private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new()
|
||||
{
|
||||
{ MySqlDbType.VarChar, "VARCHAR" },
|
||||
{ MySqlDbType.String, "CHAR" },
|
||||
{ MySqlDbType.Text, "TEXT" },
|
||||
{ MySqlDbType.TinyText, "TINYTEXT" },
|
||||
{ MySqlDbType.MediumText, "MEDIUMTEXT" },
|
||||
{ MySqlDbType.LongText, "LONGTEXT" },
|
||||
{ MySqlDbType.Float, "FLOAT" },
|
||||
{ MySqlDbType.Double, "DOUBLE" },
|
||||
{ MySqlDbType.Int32, "INT" },
|
||||
{ MySqlDbType.Int64, "BIGINT"},
|
||||
{ MySqlDbType.DateTime, "DATETIME"},
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string DbTypeToString(MySqlDbType type, int? length)
|
||||
{
|
||||
if (TypesAsStrings.TryGetValue(type, out string ret))
|
||||
{
|
||||
return ret + (length is not null ? "({0})".SFormat((int)length) : "");
|
||||
}
|
||||
|
||||
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string EscapeTableName(string table) => table.SFormat("`{0}`", table);
|
||||
}
|
||||
87
TShockAPI/DB/Queries/PostgresQueryBuilder.cs
Normal file
87
TShockAPI/DB/Queries/PostgresQueryBuilder.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
TShock, a server mod for Terraria
|
||||
Copyright (C) 2011-2025 Pryaxis & TShock Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace TShockAPI.DB.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Query Creator for PostgreSQL
|
||||
/// </summary>
|
||||
public class PostgresQueryBuilder : GenericQueryBuilder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string DbTypeToString(MySqlDbType type, int? length) => type switch
|
||||
{
|
||||
MySqlDbType.VarChar when length is not null => "VARCHAR({0})".SFormat(length),
|
||||
MySqlDbType.String when length is not null => "CHAR({0})".SFormat(length),
|
||||
MySqlDbType.Text => "TEXT",
|
||||
MySqlDbType.TinyText => "TEXT",
|
||||
MySqlDbType.MediumText => "TEXT",
|
||||
MySqlDbType.LongText => "TEXT",
|
||||
MySqlDbType.Float => "REAL",
|
||||
MySqlDbType.Double => "DOUBLE PRECISION",
|
||||
MySqlDbType.Int32 => "INT",
|
||||
MySqlDbType.Int64 => "BIGINT",
|
||||
MySqlDbType.DateTime => "TIMESTAMP",
|
||||
|
||||
_ => throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type))
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string EscapeTableName(string table) => table.SFormat("\"{0}\"", table);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string CreateTable(SqlTable table)
|
||||
{
|
||||
ValidateSqlColumnType(table.Columns);
|
||||
|
||||
IEnumerable<string> columns = table.Columns.Select(c =>
|
||||
{
|
||||
// Handle PostgreSQL-specific auto-increment using SERIAL/BIGSERIAL
|
||||
string dataType;
|
||||
|
||||
if (c.AutoIncrement)
|
||||
{
|
||||
dataType = c.Type is MySqlDbType.Int32 ? "SERIAL" : "BIGSERIAL";
|
||||
}
|
||||
else
|
||||
{
|
||||
dataType = DbTypeToString(c.Type, c.Length);
|
||||
}
|
||||
|
||||
return "{0} {1} {2} {3} {4}".SFormat(c.Name,
|
||||
dataType,
|
||||
c.Primary ? "PRIMARY KEY" : "",
|
||||
c.NotNull && !c.AutoIncrement ? "NOT NULL" : "", // SERIAL implies NOT NULL
|
||||
c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : "");
|
||||
});
|
||||
|
||||
string[] uniques = table.Columns
|
||||
.Where(c => c.Unique).Select(c => c.Name)
|
||||
.ToArray(); // No re-enumeration
|
||||
|
||||
return $"CREATE TABLE {EscapeTableName(table.Name)} ({string.Join(", ", columns)} {(uniques.Any() ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : "")})";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string RenameTable(string from, string to) => "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to);
|
||||
}
|
||||
103
TShockAPI/DB/Queries/SqliteQueryBuilder.cs
Normal file
103
TShockAPI/DB/Queries/SqliteQueryBuilder.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
TShock, a server mod for Terraria
|
||||
Copyright (C) 2011-2025 Pryaxis & TShock Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace TShockAPI.DB.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Query Creator for Sqlite
|
||||
/// </summary>
|
||||
public class SqliteQueryBuilder : GenericQueryBuilder, IQueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a table from a SqlTable object.
|
||||
/// </summary>
|
||||
/// <param name="table">The SqlTable to create the table from</param>
|
||||
/// <returns>The sql query for the table creation.</returns>
|
||||
public override string CreateTable(SqlTable table)
|
||||
{
|
||||
ValidateSqlColumnType(table.Columns);
|
||||
var columns =
|
||||
table.Columns.Select(
|
||||
c =>
|
||||
"'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name,
|
||||
DbTypeToString(c.Type, c.Length),
|
||||
c.Primary ? "PRIMARY KEY" : "",
|
||||
c.AutoIncrement ? "AUTOINCREMENT" : "",
|
||||
c.NotNull ? "NOT NULL" : "",
|
||||
c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : ""));
|
||||
var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name);
|
||||
return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name),
|
||||
string.Join(", ", columns),
|
||||
uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames the given table.
|
||||
/// </summary>
|
||||
/// <param name="from">Old name of the table</param>
|
||||
/// <param name="to">New name of the table</param>
|
||||
/// <returns>The sql query for renaming the table.</returns>
|
||||
public override string RenameTable(string from, string to)
|
||||
{
|
||||
return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string>
|
||||
{
|
||||
{ MySqlDbType.VarChar, "TEXT" },
|
||||
{ MySqlDbType.String, "TEXT" },
|
||||
{ MySqlDbType.Text, "TEXT" },
|
||||
{ MySqlDbType.TinyText, "TEXT" },
|
||||
{ MySqlDbType.MediumText, "TEXT" },
|
||||
{ MySqlDbType.LongText, "TEXT" },
|
||||
{ MySqlDbType.Float, "REAL" },
|
||||
{ MySqlDbType.Double, "REAL" },
|
||||
{ MySqlDbType.Int32, "INTEGER" },
|
||||
{ MySqlDbType.Blob, "BLOB" },
|
||||
{ MySqlDbType.Int64, "BIGINT"},
|
||||
{ MySqlDbType.DateTime, "DATETIME"},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts the MySqlDbType enum to it's string representation.
|
||||
/// </summary>
|
||||
/// <param name="type">The MySqlDbType type</param>
|
||||
/// <param name="length">The length of the datatype</param>
|
||||
/// <returns>The string representation</returns>
|
||||
public override string DbTypeToString(MySqlDbType type, int? length)
|
||||
{
|
||||
if (TypesAsStrings.TryGetValue(type, out string ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes the table name
|
||||
/// </summary>
|
||||
/// <param name="table">The name of the table to be escaped</param>
|
||||
/// <returns></returns>
|
||||
protected override string EscapeTableName(string table) => $"\'{table}\'";
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
TShock, a server mod for Terraria
|
||||
Copyright (C) 2011-2019 Pryaxis & TShock Contributors
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ using System.Linq;
|
|||
using MySql.Data.MySqlClient;
|
||||
using Terraria;
|
||||
using Microsoft.Xna.Framework;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -55,10 +56,7 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("Owner", MySqlDbType.VarChar, 50),
|
||||
new SqlColumn("Z", MySqlDbType.Int32){ DefaultValue = "0" }
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
}
|
||||
|
||||
|
|
@ -69,8 +67,8 @@ namespace TShockAPI.DB
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var reader = database.QueryReader("SELECT * FROM Regions WHERE WorldID=@0", Main.worldID.ToString()))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM Regions WHERE WorldID=@0", Main.worldID.ToString());
|
||||
|
||||
Regions.Clear();
|
||||
while (reader.Read())
|
||||
{
|
||||
|
|
@ -92,14 +90,12 @@ namespace TShockAPI.DB
|
|||
r.SetAllowedGroups(groups);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < splitids.Length; i++)
|
||||
foreach (string t in splitids)
|
||||
{
|
||||
int userid;
|
||||
|
||||
if (Int32.TryParse(splitids[i], out userid)) // if unparsable, it's not an int, so silently skip
|
||||
if (int.TryParse(t, out int userid)) // if unparsable, it's not an int, so silently skip
|
||||
r.AllowedIDs.Add(userid);
|
||||
else
|
||||
TShock.Log.Warn(GetString($"One of your UserIDs is not a usable integer: {splitids[i]}"));
|
||||
TShock.Log.Warn(GetString($"One of your UserIDs is not a usable integer: {t}"));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -113,7 +109,6 @@ namespace TShockAPI.DB
|
|||
Regions.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
@ -295,10 +290,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="x">X coordinate</param>
|
||||
/// <param name="y">Y coordinate</param>
|
||||
/// <returns>Whether any regions exist at the given (x, y) coordinate</returns>
|
||||
public bool InArea(int x, int y)
|
||||
{
|
||||
return Regions.Any(r => r.InArea(x, y));
|
||||
}
|
||||
public bool InArea(int x, int y) => Regions.Any(r => r.InArea(x, y));
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any regions exist at the given (x, y) coordinate
|
||||
|
|
@ -307,10 +299,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="x">X coordinate</param>
|
||||
/// <param name="y">Y coordinate</param>
|
||||
/// <returns>The names of any regions that exist at the given (x, y) coordinate</returns>
|
||||
public IEnumerable<string> InAreaRegionName(int x, int y)
|
||||
{
|
||||
return Regions.Where(r => r.InArea(x, y)).Select(r => r.Name);
|
||||
}
|
||||
public IEnumerable<string> InAreaRegionName(int x, int y) => Regions.Where(r => r.InArea(x, y)).Select(r => r.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any regions exist at the given (x, y) coordinate
|
||||
|
|
@ -319,10 +308,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="x">X coordinate</param>
|
||||
/// <param name="y">Y coordinate</param>
|
||||
/// <returns>The IDs of any regions that exist at the given (x, y) coordinate</returns>
|
||||
public IEnumerable<int> InAreaRegionID(int x, int y)
|
||||
{
|
||||
return Regions.Where(r => r.InArea(x, y)).Select(r => r.ID);
|
||||
}
|
||||
public IEnumerable<int> InAreaRegionID(int x, int y) => Regions.Where(r => r.InArea(x, y)).Select(r => r.ID);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any regions exist at the given (x, y) coordinate
|
||||
|
|
@ -331,10 +317,7 @@ namespace TShockAPI.DB
|
|||
/// <param name="x">X coordinate</param>
|
||||
/// <param name="y">Y coordinate</param>
|
||||
/// <returns>The <see cref="Region"/> objects of any regions that exist at the given (x, y) coordinate</returns>
|
||||
public IEnumerable<Region> InAreaRegion(int x, int y)
|
||||
{
|
||||
return Regions.Where(r => r.InArea(x, y));
|
||||
}
|
||||
public IEnumerable<Region> InAreaRegion(int x, int y) => Regions.Where(r => r.InArea(x, y));
|
||||
|
||||
/// <summary>
|
||||
/// Changes the size of a given region
|
||||
|
|
@ -413,7 +396,6 @@ namespace TShockAPI.DB
|
|||
/// <returns>true if renamed successfully, false otherwise</returns>
|
||||
public bool RenameRegion(string oldName, string newName)
|
||||
{
|
||||
Region region = null;
|
||||
string worldID = Main.worldID.ToString();
|
||||
|
||||
bool result = false;
|
||||
|
|
@ -425,7 +407,7 @@ namespace TShockAPI.DB
|
|||
|
||||
if (q > 0)
|
||||
{
|
||||
region = Regions.First(r => r.Name == oldName && r.WorldID == worldID);
|
||||
Region region = Regions.First(r => r.Name == oldName && r.WorldID == worldID);
|
||||
region.Name = newName;
|
||||
Hooks.RegionHooks.OnRegionRenamed(region, oldName, newName);
|
||||
result = true;
|
||||
|
|
@ -522,7 +504,7 @@ namespace TShockAPI.DB
|
|||
{
|
||||
try
|
||||
{
|
||||
Region region = Regions.First(r => String.Equals(regionName, r.Name, StringComparison.OrdinalIgnoreCase));
|
||||
Region region = Regions.First(r => string.Equals(regionName, r.Name, StringComparison.OrdinalIgnoreCase));
|
||||
region.Area = new Rectangle(x, y, width, height);
|
||||
|
||||
if (database.Query("UPDATE Regions SET X1 = @0, Y1 = @1, width = @2, height = @3 WHERE RegionName = @4 AND WorldID = @5",
|
||||
|
|
@ -546,12 +528,10 @@ namespace TShockAPI.DB
|
|||
var regions = new List<Region>();
|
||||
try
|
||||
{
|
||||
using (var reader = database.QueryReader("SELECT RegionName FROM Regions WHERE WorldID=@0", worldid))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT RegionName FROM Regions WHERE WorldID=@0", worldid);
|
||||
while (reader.Read())
|
||||
regions.Add(new Region {Name = reader.Get<string>("RegionName")});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
@ -564,20 +544,14 @@ namespace TShockAPI.DB
|
|||
/// </summary>
|
||||
/// <param name="name">Region name</param>
|
||||
/// <returns>The region with the given name, or null if not found</returns>
|
||||
public Region GetRegionByName(String name)
|
||||
{
|
||||
return Regions.FirstOrDefault(r => r.Name.Equals(name) && r.WorldID == Main.worldID.ToString());
|
||||
}
|
||||
public Region GetRegionByName(string name) => Regions.FirstOrDefault(r => r.Name.Equals(name) && r.WorldID == Main.worldID.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// Returns a region with the given ID
|
||||
/// </summary>
|
||||
/// <param name="id">Region ID</param>
|
||||
/// <returns>The region with the given ID, or null if not found</returns>
|
||||
public Region GetRegionByID(int id)
|
||||
{
|
||||
return Regions.FirstOrDefault(r => r.ID == id && r.WorldID == Main.worldID.ToString());
|
||||
}
|
||||
public Region GetRegionByID(int id) => Regions.FirstOrDefault(r => r.ID == id && r.WorldID == Main.worldID.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// Changes the owner of the region with the given name
|
||||
|
|
@ -798,12 +772,12 @@ namespace TShockAPI.DB
|
|||
/// Sets the user IDs which are allowed to use the region
|
||||
/// </summary>
|
||||
/// <param name="ids">String of IDs to set</param>
|
||||
public void SetAllowedIDs(String ids)
|
||||
public void SetAllowedIDs(string ids)
|
||||
{
|
||||
String[] idArr = ids.Split(',');
|
||||
string[] idArr = ids.Split(',');
|
||||
List<int> idList = new List<int>();
|
||||
|
||||
foreach (String id in idArr)
|
||||
foreach (string id in idArr)
|
||||
{
|
||||
int i = 0;
|
||||
if (int.TryParse(id, out i) && i != 0)
|
||||
|
|
@ -818,12 +792,12 @@ namespace TShockAPI.DB
|
|||
/// Sets the group names which are allowed to use the region
|
||||
/// </summary>
|
||||
/// <param name="groups">String of group names to set</param>
|
||||
public void SetAllowedGroups(String groups)
|
||||
public void SetAllowedGroups(string groups)
|
||||
{
|
||||
// prevent null pointer exceptions
|
||||
if (!string.IsNullOrEmpty(groups))
|
||||
{
|
||||
List<String> groupList = groups.Split(',').ToList();
|
||||
List<string> groupList = groups.Split(',').ToList();
|
||||
|
||||
for (int i = 0; i < groupList.Count; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using System.Data;
|
|||
using MySql.Data.MySqlClient;
|
||||
using Terraria;
|
||||
using Microsoft.Xna.Framework;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -39,10 +40,7 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("Y", MySqlDbType.Int32),
|
||||
new SqlColumn("WorldID", MySqlDbType.Text)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
}
|
||||
|
||||
|
|
@ -50,8 +48,7 @@ namespace TShockAPI.DB
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var reader = database.QueryReader("SELECT * FROM RememberedPos WHERE Name=@0", name))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM RememberedPos WHERE Name=@0", name);
|
||||
if (reader.Read())
|
||||
{
|
||||
int checkX=reader.Get<int>("X");
|
||||
|
|
@ -64,7 +61,6 @@ namespace TShockAPI.DB
|
|||
return new Vector2(checkX, checkY);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
@ -79,14 +75,12 @@ namespace TShockAPI.DB
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var reader = database.QueryReader("SELECT * FROM RememberedPos WHERE Name=@0 AND IP=@1 AND WorldID=@2", name, IP, Main.worldID.ToString()))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM RememberedPos WHERE Name=@0 AND IP=@1 AND WorldID=@2", name, IP, Main.worldID.ToString());
|
||||
if (reader.Read())
|
||||
{
|
||||
return new Vector2(reader.Get<int>("X"), reader.Get<int>("Y"));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
using Terraria;
|
||||
using Terraria.ID;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -40,10 +41,9 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("AmountSacrificed", MySqlDbType.Int32),
|
||||
new SqlColumn("TimeSacrificed", MySqlDbType.DateTime)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder)new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
|
||||
try
|
||||
{
|
||||
creator.EnsureTableStructure(table);
|
||||
|
|
@ -84,17 +84,16 @@ namespace TShockAPI.DB
|
|||
where WorldId = @0
|
||||
group by itemId";
|
||||
|
||||
try {
|
||||
using (var reader = database.QueryReader(sql, Main.worldID))
|
||||
try
|
||||
{
|
||||
using var reader = database.QueryReader(sql, Main.worldID);
|
||||
while (reader.Read())
|
||||
{
|
||||
var itemId = reader.Get<Int32>("itemId");
|
||||
var amount = reader.Get<Int32>("totalSacrificed");
|
||||
var itemId = reader.Get<int>("itemId");
|
||||
var amount = reader.Get<int>("totalSacrificed");
|
||||
sacrificedItems[itemId] = amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using System.Collections.Generic;
|
|||
using System.Data;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -58,7 +59,9 @@ namespace TShockAPI.DB
|
|||
var columns = GetColumns(table);
|
||||
if (columns.Count > 0)
|
||||
{
|
||||
if (!table.Columns.All(c => columns.Contains(c.Name)) || !columns.All(c => table.Columns.Any(c2 => c2.Name == c)))
|
||||
// Use OrdinalIgnoreCase to account for pgsql automatically lowering cases.
|
||||
if (!table.Columns.All(c => columns.Contains(c.Name, StringComparer.OrdinalIgnoreCase))
|
||||
|| !columns.All(c => table.Columns.Any(c2 => c2.Name.Equals(c, StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
var from = new SqlTable(table.Name, columns.Select(s => new SqlColumn(s, MySqlDbType.String)).ToList());
|
||||
database.Query(creator.AlterTable(from, table));
|
||||
|
|
@ -69,36 +72,50 @@ namespace TShockAPI.DB
|
|||
database.Query(creator.CreateTable(table));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<string> GetColumns(SqlTable table)
|
||||
{
|
||||
var ret = new List<string>();
|
||||
var name = database.GetSqlType();
|
||||
if (name == SqlType.Sqlite)
|
||||
List<string> ret = new();
|
||||
switch (database.GetSqlType())
|
||||
{
|
||||
using (var reader = database.QueryReader("PRAGMA table_info({0})".SFormat(table.Name)))
|
||||
case SqlType.Sqlite:
|
||||
{
|
||||
using QueryResult reader = database.QueryReader("PRAGMA table_info({0})".SFormat(table.Name));
|
||||
while (reader.Read())
|
||||
{
|
||||
ret.Add(reader.Get<string>("name"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else if (name == SqlType.Mysql)
|
||||
{
|
||||
using (
|
||||
var reader =
|
||||
database.QueryReader(
|
||||
"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME=@0 AND TABLE_SCHEMA=@1", table.Name,
|
||||
database.Database))
|
||||
case SqlType.Mysql:
|
||||
{
|
||||
using QueryResult reader =
|
||||
database.QueryReader("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME=@0 AND TABLE_SCHEMA=@1", table.Name, database.Database);
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
ret.Add(reader.Get<string>("COLUMN_NAME"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
case SqlType.Postgres:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
// HACK: Using "ilike" op to ignore case, due to weird case issues adapting for pgsql
|
||||
using QueryResult reader = database.QueryReader("SELECT column_name FROM information_schema.columns WHERE table_schema=current_schema() AND table_name ILIKE @0", table.Name);
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
ret.Add(reader.Get<string>("column_name"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: throw new NotSupportedException();
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -58,11 +59,9 @@ namespace TShockAPI.DB
|
|||
{
|
||||
List<object> values = new List<object>();
|
||||
|
||||
using (var reader = database.QueryReader(creator.ReadColumn(table, wheres)))
|
||||
{
|
||||
using var reader = database.QueryReader(creator.ReadColumn(table, wheres));
|
||||
while (reader.Read())
|
||||
values.Add(reader.Reader.Get<object>(column));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using System.Collections.Generic;
|
|||
using System.Data;
|
||||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
using TShockAPI.DB.Queries;
|
||||
using TShockAPI.Hooks;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
|
|
@ -38,10 +39,8 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("TileId", MySqlDbType.Int32) { Primary = true },
|
||||
new SqlColumn("AllowedGroups", MySqlDbType.Text)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder)new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
UpdateBans();
|
||||
}
|
||||
|
|
@ -50,16 +49,14 @@ namespace TShockAPI.DB
|
|||
{
|
||||
TileBans.Clear();
|
||||
|
||||
using (var reader = database.QueryReader("SELECT * FROM TileBans"))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM TileBans");
|
||||
while (reader != null && reader.Read())
|
||||
{
|
||||
TileBan ban = new TileBan((short)reader.Get<Int32>("TileId"));
|
||||
TileBan ban = new TileBan((short)reader.Get<int>("TileId"));
|
||||
ban.SetAllowedGroups(reader.Get<string>("AllowedGroups"));
|
||||
TileBans.Add(ban);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewBan(short id = 0)
|
||||
{
|
||||
|
|
@ -119,7 +116,7 @@ namespace TShockAPI.DB
|
|||
{
|
||||
try
|
||||
{
|
||||
groupsNew = String.Join(",", b.AllowedGroups);
|
||||
groupsNew = string.Join(",", b.AllowedGroups);
|
||||
if (groupsNew.Length > 0)
|
||||
groupsNew += ",";
|
||||
groupsNew += name;
|
||||
|
|
@ -229,12 +226,12 @@ namespace TShockAPI.DB
|
|||
// could add in the other permissions in this class instead of a giant if switch.
|
||||
}
|
||||
|
||||
public void SetAllowedGroups(String groups)
|
||||
public void SetAllowedGroups(string groups)
|
||||
{
|
||||
// prevent null pointer exceptions
|
||||
if (!string.IsNullOrEmpty(groups))
|
||||
{
|
||||
List<String> groupArr = groups.Split(',').ToList();
|
||||
List<string> groupArr = groups.Split(',').ToList();
|
||||
|
||||
for (int i = 0; i < groupArr.Count; i++)
|
||||
{
|
||||
|
|
@ -252,7 +249,7 @@ namespace TShockAPI.DB
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return ID + (AllowedGroups.Count > 0 ? " (" + String.Join(",", AllowedGroups) + ")" : "");
|
||||
return ID + (AllowedGroups.Count > 0 ? " (" + string.Join(",", AllowedGroups) + ")" : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ using MySql.Data.MySqlClient;
|
|||
using System.Text.RegularExpressions;
|
||||
using BCrypt.Net;
|
||||
using System.Security.Cryptography;
|
||||
using TShockAPI.DB.Queries;
|
||||
using TShockAPI.Hooks;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
|
|
@ -52,10 +53,8 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("LastAccessed", MySqlDbType.Text),
|
||||
new SqlColumn("KnownIPs", MySqlDbType.Text)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
}
|
||||
|
||||
|
|
@ -240,14 +239,12 @@ namespace TShockAPI.DB
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var reader = _database.QueryReader("SELECT * FROM Users WHERE Username=@0", username))
|
||||
{
|
||||
using var reader = _database.QueryReader("SELECT * FROM Users WHERE Username=@0", username);
|
||||
if (reader.Read())
|
||||
{
|
||||
return reader.Get<int>("ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.ConsoleError(GetString($"FetchHashedPasswordAndGroup SQL returned an error: {ex}"));
|
||||
|
|
@ -309,8 +306,7 @@ namespace TShockAPI.DB
|
|||
|
||||
try
|
||||
{
|
||||
using (var result = _database.QueryReader(query, arg))
|
||||
{
|
||||
using var result = _database.QueryReader(query, arg);
|
||||
if (result.Read())
|
||||
{
|
||||
account = LoadUserAccountFromResult(account, result);
|
||||
|
|
@ -320,7 +316,6 @@ namespace TShockAPI.DB
|
|||
multiple = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new UserAccountManagerException(GetString($"GetUser SQL returned an error {ex.Message}"), ex);
|
||||
|
|
@ -338,15 +333,13 @@ namespace TShockAPI.DB
|
|||
try
|
||||
{
|
||||
List<UserAccount> accounts = new List<UserAccount>();
|
||||
using (var reader = _database.QueryReader("SELECT * FROM Users"))
|
||||
{
|
||||
using var reader = _database.QueryReader("SELECT * FROM Users");
|
||||
while (reader.Read())
|
||||
{
|
||||
accounts.Add(LoadUserAccountFromResult(new UserAccount(), reader));
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TShock.Log.Error(ex.ToString());
|
||||
|
|
@ -366,14 +359,13 @@ namespace TShockAPI.DB
|
|||
{
|
||||
List<UserAccount> accounts = new List<UserAccount>();
|
||||
string search = notAtStart ? string.Format("%{0}%", username) : string.Format("{0}%", username);
|
||||
using (var reader = _database.QueryReader("SELECT * FROM Users WHERE Username LIKE @0",
|
||||
search))
|
||||
{
|
||||
using var reader = _database.QueryReader("SELECT * FROM Users WHERE Username LIKE @0",
|
||||
search);
|
||||
while (reader.Read())
|
||||
{
|
||||
accounts.Add(LoadUserAccountFromResult(new UserAccount(), reader));
|
||||
}
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -496,7 +488,7 @@ namespace TShockAPI.DB
|
|||
int currentWorkFactor;
|
||||
try
|
||||
{
|
||||
currentWorkFactor = Int32.Parse((Password.Split('$')[2]));
|
||||
currentWorkFactor = int.Parse((Password.Split('$')[2]));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ using System.Linq;
|
|||
using MySql.Data.MySqlClient;
|
||||
using Terraria;
|
||||
using Microsoft.Xna.Framework;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -48,10 +49,8 @@ namespace TShockAPI.DB
|
|||
new SqlColumn("WorldID", MySqlDbType.VarChar, 50) { Unique = true },
|
||||
new SqlColumn("Private", MySqlDbType.Text)
|
||||
);
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
}
|
||||
|
||||
|
|
@ -87,9 +86,8 @@ namespace TShockAPI.DB
|
|||
{
|
||||
Warps.Clear();
|
||||
|
||||
using (var reader = database.QueryReader("SELECT * FROM Warps WHERE WorldID = @0",
|
||||
Main.worldID.ToString()))
|
||||
{
|
||||
using var reader = database.QueryReader("SELECT * FROM Warps WHERE WorldID = @0",
|
||||
Main.worldID.ToString());
|
||||
while (reader.Read())
|
||||
{
|
||||
Warps.Add(new Warp(
|
||||
|
|
@ -98,7 +96,6 @@ namespace TShockAPI.DB
|
|||
(reader.Get<string>("Private") ?? "0") != "0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a warp.
|
||||
|
|
@ -112,7 +109,7 @@ namespace TShockAPI.DB
|
|||
if (database.Query("DELETE FROM Warps WHERE WarpName = @0 AND WorldID = @1",
|
||||
warpName, Main.worldID.ToString()) > 0)
|
||||
{
|
||||
Warps.RemoveAll(w => String.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase));
|
||||
Warps.RemoveAll(w => string.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +127,7 @@ namespace TShockAPI.DB
|
|||
/// <returns>The warp, if it exists, or else null.</returns>
|
||||
public Warp Find(string warpName)
|
||||
{
|
||||
return Warps.FirstOrDefault(w => String.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase));
|
||||
return Warps.FirstOrDefault(w => string.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -147,7 +144,7 @@ namespace TShockAPI.DB
|
|||
if (database.Query("UPDATE Warps SET X = @0, Y = @1 WHERE WarpName = @2 AND WorldID = @3",
|
||||
x, y, warpName, Main.worldID.ToString()) > 0)
|
||||
{
|
||||
Warps.Find(w => String.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase)).Position = new Point(x, y);
|
||||
Warps.Find(w => string.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase)).Position = new Point(x, y);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -171,7 +168,7 @@ namespace TShockAPI.DB
|
|||
if (database.Query("UPDATE Warps SET Private = @0 WHERE WarpName = @1 AND WorldID = @2",
|
||||
state ? "1" : "0", warpName, Main.worldID.ToString()) > 0)
|
||||
{
|
||||
Warps.Find(w => String.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase)).IsPrivate = state;
|
||||
Warps.Find(w => string.Equals(w.Name, warpName, StringComparison.OrdinalIgnoreCase)).IsPrivate = state;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Npgsql;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI.DB
|
||||
{
|
||||
|
|
@ -38,18 +42,19 @@ namespace TShockAPI.DB
|
|||
[SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
|
||||
public static int Query(this IDbConnection olddb, string query, params object[] args)
|
||||
{
|
||||
using (var db = olddb.CloneEx())
|
||||
{
|
||||
using var db = olddb.CloneEx();
|
||||
db.Open();
|
||||
using (var com = db.CreateCommand())
|
||||
{
|
||||
|
||||
using var com = db.CreateCommand();
|
||||
com.CommandText = query;
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
com.AddParameter("@" + i, args[i] ?? DBNull.Value);
|
||||
{
|
||||
com.AddParameter($"@{i}", args[i] ?? DBNull.Value);
|
||||
}
|
||||
|
||||
return com.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a query on a database.
|
||||
|
|
@ -143,15 +148,21 @@ namespace TShockAPI.DB
|
|||
return clone;
|
||||
}
|
||||
|
||||
public static SqlType GetSqlType(this IDbConnection conn)
|
||||
public static SqlType GetSqlType(this IDbConnection conn) => conn switch
|
||||
{
|
||||
var name = conn.GetType().Name;
|
||||
if (name == "SqliteConnection" || name == "SQLiteConnection")
|
||||
return SqlType.Sqlite;
|
||||
if (name == "MySqlConnection")
|
||||
return SqlType.Mysql;
|
||||
return SqlType.Unknown;
|
||||
}
|
||||
SqliteConnection => SqlType.Sqlite,
|
||||
MySqlConnection => SqlType.Mysql,
|
||||
NpgsqlConnection => SqlType.Postgres,
|
||||
_ => SqlType.Unknown
|
||||
};
|
||||
|
||||
public static IQueryBuilder GetSqlQueryBuilder(this IDbConnection db) => db.GetSqlType() switch
|
||||
{
|
||||
SqlType.Sqlite => new SqliteQueryBuilder(),
|
||||
SqlType.Mysql => new MysqlQueryBuilder(),
|
||||
SqlType.Postgres => new PostgresQueryBuilder(),
|
||||
_ => throw new NotSupportedException("Database type not supported.")
|
||||
};
|
||||
|
||||
private static readonly Dictionary<Type, Func<IDataReader, int, object>> ReadFuncs = new Dictionary
|
||||
<Type, Func<IDataReader, int, object>>
|
||||
|
|
@ -267,7 +278,8 @@ namespace TShockAPI.DB
|
|||
{
|
||||
Unknown,
|
||||
Sqlite,
|
||||
Mysql
|
||||
Mysql,
|
||||
Postgres
|
||||
}
|
||||
|
||||
public class QueryResult : IDisposable
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ namespace TShockAPI
|
|||
public static class StringExt
|
||||
{
|
||||
//Can't name it Format :(
|
||||
public static String SFormat(this String str, params object[] args)
|
||||
public static string SFormat(this string str, params object[] args)
|
||||
{
|
||||
return String.Format(str, args);
|
||||
return string.Format(str, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -2778,6 +2778,9 @@ namespace TShockAPI
|
|||
return false;
|
||||
}
|
||||
|
||||
// spawn the player before teleporting
|
||||
NetMessage.SendData((int)PacketTypes.PlayerSpawn, -1, args.Player.Index, null, args.Player.Index, (int)PlayerSpawnContext.ReviveFromDeath);
|
||||
|
||||
// the player has not changed his spawnpoint yet, so we assert the server-saved spawnpoint
|
||||
// by teleporting the player instead of letting the game use the client's incorrect spawnpoint.
|
||||
TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleSpawn force ssc teleport for {0} at ({1},{2})", args.Player.Name, args.TPlayer.SpawnX, args.TPlayer.SpawnY));
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using MySql.Data.MySqlClient;
|
||||
using TShockAPI.DB;
|
||||
using TShockAPI.DB.Queries;
|
||||
|
||||
namespace TShockAPI
|
||||
{
|
||||
|
|
@ -61,7 +62,7 @@ namespace TShockAPI
|
|||
/// <param name="clearTextLog"></param>
|
||||
public SqlLog(IDbConnection db, string textlogFilepath, bool clearTextLog)
|
||||
{
|
||||
FileName = string.Format("{0}://database", db.GetSqlType());
|
||||
FileName = $"{db.GetSqlType()}://database";
|
||||
_database = db;
|
||||
_backupLog = new TextLog(textlogFilepath, clearTextLog);
|
||||
|
||||
|
|
@ -73,10 +74,7 @@ namespace TShockAPI
|
|||
new SqlColumn("Message", MySqlDbType.Text)
|
||||
);
|
||||
|
||||
var creator = new SqlTableCreator(db,
|
||||
db.GetSqlType() == SqlType.Sqlite
|
||||
? (IQueryBuilder) new SqliteQueryCreator()
|
||||
: new MysqlQueryCreator());
|
||||
SqlTableCreator creator = new(db, db.GetSqlQueryBuilder());
|
||||
creator.EnsureTableStructure(table);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ namespace TShockAPI
|
|||
/// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary>
|
||||
public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
/// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary>
|
||||
public static readonly string VersionCodename = "Stargazer";
|
||||
public static readonly string VersionCodename = "Hopefully SSC works somewhat correctly now edition";
|
||||
|
||||
/// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary>
|
||||
public static string SavePath = "tshock";
|
||||
|
|
@ -314,37 +314,9 @@ namespace TShockAPI
|
|||
// Further exceptions are written to TShock's log from now on.
|
||||
try
|
||||
{
|
||||
if (Config.Settings.StorageType.ToLower() == "sqlite")
|
||||
{
|
||||
string sql = Path.Combine(SavePath, Config.Settings.SqliteDBPath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(sql));
|
||||
DB = new Microsoft.Data.Sqlite.SqliteConnection(string.Format("Data Source={0}", sql));
|
||||
}
|
||||
else if (Config.Settings.StorageType.ToLower() == "mysql")
|
||||
{
|
||||
try
|
||||
{
|
||||
var hostport = Config.Settings.MySqlHost.Split(':');
|
||||
DB = new MySqlConnection();
|
||||
DB.ConnectionString =
|
||||
String.Format("Server={0}; Port={1}; Database={2}; Uid={3}; Pwd={4};",
|
||||
hostport[0],
|
||||
hostport.Length > 1 ? hostport[1] : "3306",
|
||||
Config.Settings.MySqlDbName,
|
||||
Config.Settings.MySqlUsername,
|
||||
Config.Settings.MySqlPassword
|
||||
);
|
||||
}
|
||||
catch (MySqlException ex)
|
||||
{
|
||||
ServerApi.LogWriter.PluginWriteLine(this, ex.ToString(), TraceLevel.Error);
|
||||
throw new Exception("MySql not setup correctly");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Invalid storage type");
|
||||
}
|
||||
// Build database
|
||||
DbBuilder dbBuilder = new(this, Config, SavePath);
|
||||
DB = dbBuilder.BuildDbConnection();
|
||||
|
||||
if (Config.Settings.UseSqlLogs)
|
||||
Log = new SqlLog(DB, logFilename, LogClear);
|
||||
|
|
@ -428,8 +400,6 @@ namespace TShockAPI
|
|||
Hooks.AccountHooks.AccountDelete += OnAccountDelete;
|
||||
Hooks.AccountHooks.AccountCreate += OnAccountCreate;
|
||||
|
||||
On.Terraria.RemoteClient.Reset += RemoteClient_Reset;
|
||||
|
||||
GetDataHandlers.InitGetDataHandler();
|
||||
Commands.InitCommands();
|
||||
|
||||
|
|
@ -449,7 +419,7 @@ namespace TShockAPI
|
|||
// Initialize the AchievementManager, which is normally only done on clients.
|
||||
Game._achievements = new AchievementManager();
|
||||
|
||||
IL.Terraria.Initializers.AchievementInitializer.Load += OnAchievementInitializerLoad;
|
||||
OTAPI.Hooks.Initializers.AchievementInitializerLoad += OnAchievementInitializerLoad;
|
||||
|
||||
// Actually call AchievementInitializer.Load, which is also normally only done on clients.
|
||||
AchievementInitializer.Load();
|
||||
|
|
@ -498,17 +468,9 @@ namespace TShockAPI
|
|||
}
|
||||
}
|
||||
|
||||
private static void RemoteClient_Reset(On.Terraria.RemoteClient.orig_Reset orig, RemoteClient client)
|
||||
private static void OnAchievementInitializerLoad(object sender, OTAPI.Hooks.Initializers.AchievementInitializerLoadEventArgs args)
|
||||
{
|
||||
client.ClientUUID = null;
|
||||
orig(client);
|
||||
}
|
||||
|
||||
private static void OnAchievementInitializerLoad(ILContext il)
|
||||
{
|
||||
// Modify AchievementInitializer.Load to remove the Main.netMode == 2 check (occupies the first 4 IL instructions)
|
||||
for (var i = 0; i < 4; i++)
|
||||
il.Body.Instructions.RemoveAt(0);
|
||||
args.ShouldLoad = true;
|
||||
}
|
||||
|
||||
protected void CrashReporter_HeapshotRequesting(object sender, EventArgs e)
|
||||
|
|
@ -532,7 +494,7 @@ namespace TShockAPI
|
|||
}
|
||||
SaveManager.Instance.Dispose();
|
||||
|
||||
IL.Terraria.Initializers.AchievementInitializer.Load -= OnAchievementInitializerLoad;
|
||||
OTAPI.Hooks.Initializers.AchievementInitializerLoad -= OnAchievementInitializerLoad;
|
||||
|
||||
ModuleManager.Dispose();
|
||||
|
||||
|
|
@ -1469,8 +1431,8 @@ namespace TShockAPI
|
|||
Hooks.PlayerHooks.OnPlayerLogout(tsplr);
|
||||
}
|
||||
|
||||
// The last player will leave after this hook is executed.
|
||||
if (Utils.GetActivePlayerCount() == 1)
|
||||
// If this is the last player online, update the console title and save the world if needed
|
||||
if (Utils.GetActivePlayerCount() == 0)
|
||||
{
|
||||
if (Config.Settings.SaveWorldOnLastPlayerExit)
|
||||
SaveManager.Instance.SaveWorld();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<!--
|
||||
|
|
@ -18,11 +18,11 @@
|
|||
Also, be sure to release on github with the exact assembly version tag as below
|
||||
so that the update manager works correctly (via the Github releases api and mimic)
|
||||
-->
|
||||
<Version>5.2.3</Version>
|
||||
<Version>5.2.4</Version>
|
||||
<AssemblyTitle>TShock for Terraria</AssemblyTitle>
|
||||
<Company>Pryaxis & TShock Contributors</Company>
|
||||
<Product>TShockAPI</Product>
|
||||
<Copyright>Copyright © Pryaxis & TShock Contributors 2011-2023</Copyright>
|
||||
<Copyright>Copyright © Pryaxis & TShock Contributors 2011-2025</Copyright>
|
||||
<!-- extras for nuget -->
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
|
||||
|
|
@ -33,9 +33,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="GetText.NET" Version="1.7.14" />
|
||||
<PackageReference Include="MySql.Data" Version="8.4.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.11" />
|
||||
<PackageReference Include="GetText.NET" Version="8.0.5" />
|
||||
<PackageReference Include="MySql.Data" Version="9.1.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ if (arch is null)
|
|||
|
||||
string? url = null;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
url = $"https://dotnetcli.azureedge.net/dotnet/Runtime/6.0.11/dotnet-runtime-6.0.11-osx-{arch}.tar.gz";
|
||||
url = $"https://dotnetcli.azureedge.net/dotnet/Runtime/9.0.0/dotnet-runtime-9.0.0-osx-{arch}.tar.gz";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
url = $"https://dotnetcli.azureedge.net/dotnet/Runtime/6.0.11/dotnet-runtime-6.0.11-win-{arch}.zip";
|
||||
url = $"https://dotnetcli.azureedge.net/dotnet/Runtime/9.0.0/dotnet-runtime-9.0.0-win-{arch}.zip";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
url = $"https://dotnetcli.azureedge.net/dotnet/Runtime/6.0.11/dotnet-runtime-6.0.11-linux-{arch}.tar.gz";
|
||||
url = $"https://dotnetcli.azureedge.net/dotnet/Runtime/9.0.0/dotnet-runtime-9.0.0-linux-{arch}.tar.gz";
|
||||
|
||||
if(url is null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>5.0.0</Version>
|
||||
|
|
|
|||
|
|
@ -21,17 +21,17 @@ public class GroupTests
|
|||
groups.AddPermissions("test", new() { "abc" });
|
||||
|
||||
var hasperm = groups.GetGroupByName("test").Permissions.Contains("abc");
|
||||
Assert.IsTrue(hasperm);
|
||||
Assert.That(hasperm, Is.True);
|
||||
|
||||
groups.DeletePermissions("test", new() { "abc" });
|
||||
|
||||
hasperm = groups.GetGroupByName("test").Permissions.Contains("abc");
|
||||
Assert.IsFalse(hasperm);
|
||||
Assert.That(hasperm, Is.False);
|
||||
|
||||
groups.DeleteGroup("test");
|
||||
|
||||
var g = groups.GetGroupByName("test");
|
||||
Assert.IsNull(g);
|
||||
Assert.That(g, Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,20 +14,21 @@ public class ServerInitTests
|
|||
public void EnsureBoots()
|
||||
{
|
||||
var are = new AutoResetEvent(false);
|
||||
On.Terraria.Main.hook_DedServ cb = (On.Terraria.Main.orig_DedServ orig, Terraria.Main instance) =>
|
||||
HookEvents.HookDelegate<Terraria.Main, HookEvents.Terraria.Main.DedServEventArgs> cb = (instance, args) =>
|
||||
{
|
||||
args.ContinueExecution = false;
|
||||
are.Set();
|
||||
Debug.WriteLine("Server init process successful");
|
||||
};
|
||||
On.Terraria.Main.DedServ += cb;
|
||||
HookEvents.Terraria.Main.DedServ += cb;
|
||||
|
||||
new Thread(() => TerrariaApi.Server.Program.Main(new string[] { })).Start();
|
||||
new Thread(() => TerrariaApi.Server.Program.Main([])).Start();
|
||||
|
||||
var hit = are.WaitOne(TimeSpan.FromSeconds(10));
|
||||
|
||||
On.Terraria.Main.DedServ -= cb;
|
||||
HookEvents.Terraria.Main.DedServ -= cb;
|
||||
|
||||
Assert.IsTrue(hit);
|
||||
Assert.That(hit, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="NUnit" Version="4.3.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.6.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -47,20 +47,11 @@ if (File.Exists("TerrariaServer.exe"))
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0].ToLower() == "plugins")
|
||||
{
|
||||
var items = args.ToList();
|
||||
items.RemoveAt(0);
|
||||
await NugetCLI.Main(items);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Dictionary<string, Assembly> _cache = new Dictionary<string, Assembly>();
|
||||
|
||||
System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += Default_Resolving;
|
||||
|
||||
return Start();
|
||||
return await StartAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a module from the ./bin folder, either with a .dll by preference or .exe
|
||||
|
|
@ -71,6 +62,7 @@ Assembly? Default_Resolving(System.Runtime.Loader.AssemblyLoadContext arg1, Asse
|
|||
if (_cache.TryGetValue(arg2.Name, out Assembly? asm) && asm is not null) return asm;
|
||||
|
||||
var loc = Path.Combine(AppContext.BaseDirectory, "bin", arg2.Name + ".dll");
|
||||
|
||||
if (File.Exists(loc))
|
||||
asm = arg1.LoadFromAssemblyPath(loc);
|
||||
|
||||
|
|
@ -88,8 +80,16 @@ Assembly? Default_Resolving(System.Runtime.Loader.AssemblyLoadContext arg1, Asse
|
|||
/// Initiates the TSAPI server.
|
||||
/// </summary>
|
||||
/// <remarks>This method exists so that the resolver can attach before TSAPI needs its dependencies.</remarks>
|
||||
int Start()
|
||||
async Task<int> StartAsync()
|
||||
{
|
||||
if (args.Length > 0 && args[0].ToLower() == "plugins")
|
||||
{
|
||||
var items = args.ToList();
|
||||
items.RemoveAt(0);
|
||||
await TShockPluginManager.NugetCLI.Main(items);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TerrariaApi.Server.Program.Main(args);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>TShock.Server</AssemblyName> <!-- TShock was initially decided on by a community poll, however tshock already exists as a folder and will clash -->
|
||||
|
|
@ -30,22 +30,24 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="MySql.Data" Version="8.4.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.11" />
|
||||
<PackageReference Include="ModFramework" Version="1.1.7" GeneratePathProperty="true" /> <!-- only used to extract out to ./bin. -->
|
||||
<PackageReference Include="GetText.NET" Version="1.7.14" /> <!-- only used to extract out to ./bin. -->
|
||||
<PackageReference Include="MySql.Data" Version="9.1.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
|
||||
<PackageReference Include="ModFramework" Version="1.1.13" GeneratePathProperty="true" /> <!-- only used to extract out to ./bin. -->
|
||||
<PackageReference Include="GetText.NET" Version="8.0.5" /> <!-- only used to extract out to ./bin. -->
|
||||
|
||||
<!-- the launcher doesnt need the direct OTAPI reference, but since PackageReference[ExcludeFromSingleFile] doesnt work, exclude the assets and copy manually -->
|
||||
<PackageReference Include="OTAPI.Upcoming" Version="3.1.20" ExcludeAssets="all" GeneratePathProperty="true" />
|
||||
<None Include="$(PkgOTAPI_Upcoming)\lib\net6.0\OTAPI.dll">
|
||||
<PackageReference Include="OTAPI.Upcoming" Version="3.2.4" ExcludeAssets="all" GeneratePathProperty="true" />
|
||||
<None Include="$(PkgOTAPI_Upcoming)\lib\net9.0\OTAPI.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
<None Include="$(PkgOTAPI_Upcoming)\lib\net6.0\OTAPI.Runtime.dll">
|
||||
<None Include="$(PkgOTAPI_Upcoming)\lib\net9.0\OTAPI.Runtime.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
<None Include="$(PkgModFramework)\lib\net6.0\ModFramework.dll">
|
||||
<None Include="$(PkgModFramework)\lib\net9.0\ModFramework.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</None>
|
||||
|
|
@ -91,7 +93,13 @@
|
|||
</ItemGroup>
|
||||
<Copy SourceFiles="@(MOFiles)" DestinationFolder="$(PublishDir)%(RecursiveDir)" />
|
||||
</Target>
|
||||
<Target Name="MoveBin" AfterTargets="Publish">
|
||||
<Target Name="MoveDevBin" AfterTargets="PostBuildEvent">
|
||||
<ItemGroup>
|
||||
<MoveBinaries Include="$(OutDir)*" Exclude="$(OutDir)\TShock.Server*;$(OutDir)\GeoIP.dat" />
|
||||
</ItemGroup>
|
||||
<Move SourceFiles="@(MoveBinaries)" DestinationFolder="$(OutDir)bin" ContinueOnError="true" />
|
||||
</Target>
|
||||
<Target Name="MovePublishBin" AfterTargets="Publish">
|
||||
<ItemGroup>
|
||||
<MoveBinaries Include="$(PublishDir)*" Exclude="$(PublishDir)\TShock.Server*;$(PublishDir)\GeoIP.dat" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ namespace TShockPluginManager
|
|||
public Nugetter()
|
||||
{
|
||||
FrameworkReducer = new FrameworkReducer();
|
||||
NuGetFramework = NuGetFramework.ParseFolder("net6.0");
|
||||
NuGetFramework = NuGetFramework.ParseFolder("net9.0");
|
||||
Settings = NuGet.Configuration.Settings.LoadDefaultSettings(root: null);
|
||||
PathContext = NuGetPathContext.Create(Settings);
|
||||
PackageSourceProvider = new PackageSourceProvider(Settings);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NuGet.Packaging" Version="6.3.4" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="6.3.3" />
|
||||
<PackageReference Include="NuGet.Resolver" Version="6.3.1" />
|
||||
<PackageReference Include="NuGet.Packaging" Version="6.12.1" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="6.12.1" />
|
||||
<PackageReference Include="NuGet.Resolver" Version="6.12.1" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="6.0.0" />
|
||||
<PackageReference Include="GetText.NET" Version="1.7.14" />
|
||||
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.0" />
|
||||
<PackageReference Include="GetText.NET" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit d4bb7e3a21e875cfeb23bcf5cf847c85d9470ccf
|
||||
Subproject commit 29dc46f4e1c7b41f9e88e41187dd8d5b208e257a
|
||||
|
|
@ -9,5 +9,5 @@ build_script:
|
|||
|
||||
dotnet test
|
||||
artifacts:
|
||||
- path: ./TShockLauncher/bin/Debug/net6.0
|
||||
- path: ./TShockLauncher/bin/Debug/net9.0
|
||||
name: TShockAVDebug
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue