Skip to content

Commit eb22e39

Browse files
committed
v1.0.6
1 parent f510b71 commit eb22e39

File tree

6 files changed

+147
-51
lines changed

6 files changed

+147
-51
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>dev.felnull</groupId>
88
<artifactId>BetterStorage</artifactId>
9-
<version>1.0.6-SNAPSHOT</version>
9+
<version>1.0.6</version>
1010
<packaging>jar</packaging>
1111

1212
<name>BetterStorage</name>

readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ BetterStorage は、プレイヤーごと・グループごとにインベント
1212
- UUIDベースの識別(名前変更でも安全)
1313
- インベントリやタグの差分を自動保存
1414
- ロールバック機能搭載(過去の状態に戻せる)
15-
- Redisや他プラグインとの連携を想定したAPI設計
15+
- 他プラグインとの連携を想定したAPI設計
1616
- 別プラグインによるGUI制御が可能(BetterStorageはバックエンド)
1717

1818
## 対応バージョン
@@ -47,7 +47,7 @@ BetterStorage は、プレイヤーごと・グループごとにインベント
4747

4848
## 開発API
4949

50-
BetterStorageは外部プラグインから `GroupData`, `InventoryData` などのデータ取得や制御が可能です。 詳細は [API.md](API.md) を参照してください。
50+
BetterStorageは外部プラグインから `GroupData`, `InventoryData` などのデータ取得や制御が可能です。
5151

5252
## ライセンス
5353

src/main/java/dev/felnull/DataIO/DataIO.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.sql.*;
1717
import java.time.LocalDateTime;
1818
import java.util.*;
19+
import java.util.stream.Collectors;
1920

2021
public class DataIO {
2122
private static final Gson gson = new Gson();
@@ -82,11 +83,10 @@ public static boolean saveGroupData(GroupData g, long clientVersion) {
8283
}
8384

8485
private static void saveSinglePage(Connection conn, GroupData g, String pageId, InventoryData inv) throws SQLException {
85-
// inventory_table
86+
// ---------- inventory_table ----------
8687
String invSql = "REPLACE INTO inventory_table (group_uuid, plugin_name, page_id, display_name, row_count, require_permission) VALUES (?, ?, ?, ?, ?, ?)";
8788
try (PreparedStatement ps = conn.prepareStatement(invSql)) {
8889
ps.setString(1, g.groupUUID.toString());
89-
// plugin_name にはこのStorageページを管理するGUI側プラグイン名を指定(例: "StorageGUI")
9090
ps.setString(2, g.ownerPlugin);
9191
ps.setString(3, pageId);
9292
ps.setString(4, inv.displayName);
@@ -95,13 +95,50 @@ private static void saveSinglePage(Connection conn, GroupData g, String pageId,
9595
ps.executeUpdate();
9696
}
9797

98-
// inventory_item_table
98+
// ---------- inventory_item_table ----------
99+
// 🔥 1. 既存スロットを取得
100+
Set<Integer> currentSlots = inv.itemStackSlot.keySet();
101+
Set<Integer> oldSlots = new HashSet<>();
102+
String fetchSql = "SELECT slot FROM inventory_item_table WHERE group_uuid = ? AND page_id = ?";
103+
try (PreparedStatement ps = conn.prepareStatement(fetchSql)) {
104+
ps.setString(1, g.groupUUID.toString());
105+
ps.setString(2, pageId);
106+
try (ResultSet rs = ps.executeQuery()) {
107+
while (rs.next()) {
108+
oldSlots.add(rs.getInt("slot"));
109+
}
110+
}
111+
}
112+
113+
// 🔥 2. 削除されたスロットを DELETE
114+
if (!oldSlots.isEmpty()) {
115+
Set<Integer> slotsToDelete = new HashSet<>(oldSlots);
116+
slotsToDelete.removeAll(currentSlots);
117+
118+
if (!slotsToDelete.isEmpty()) {
119+
StringBuilder sb = new StringBuilder();
120+
sb.append("DELETE FROM inventory_item_table WHERE group_uuid = ? AND page_id = ? AND slot IN (");
121+
sb.append(slotsToDelete.stream().map(s -> "?").collect(Collectors.joining(",")));
122+
sb.append(")");
123+
124+
try (PreparedStatement ps = conn.prepareStatement(sb.toString())) {
125+
ps.setString(1, g.groupUUID.toString());
126+
ps.setString(2, pageId);
127+
int index = 3;
128+
for (Integer slot : slotsToDelete) {
129+
ps.setInt(index++, slot);
130+
}
131+
ps.executeUpdate();
132+
}
133+
}
134+
}
135+
136+
// 🔥 3. 現在のアイテムを REPLACE
99137
String itemSql = "REPLACE INTO inventory_item_table (group_uuid, plugin_name, page_id, slot, itemstack, display_name, material, amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
100138
try (PreparedStatement ps = conn.prepareStatement(itemSql)) {
101139
for (Map.Entry<Integer, ItemStack> itemEntry : inv.itemStackSlot.entrySet()) {
102140
ItemStack item = itemEntry.getValue();
103141
ps.setString(1, g.groupUUID.toString());
104-
// plugin_name にはこのStorageページを管理するGUI側プラグイン名を指定(例: "StorageGUI")
105142
ps.setString(2, g.ownerPlugin);
106143
ps.setString(3, pageId);
107144
ps.setInt(4, itemEntry.getKey());
@@ -114,13 +151,12 @@ private static void saveSinglePage(Connection conn, GroupData g, String pageId,
114151
ps.executeBatch();
115152
}
116153

117-
// tag_table
154+
// ---------- tag_table ----------
118155
String tagSql = "REPLACE INTO tag_table (group_uuid, plugin_name, page_id, user_tag) VALUES (?, ?, ?, ?)";
119156
try (PreparedStatement ps = conn.prepareStatement(tagSql)) {
120157
if (inv.userTags != null && !inv.userTags.isEmpty()) {
121158
for (String tag : inv.userTags) {
122159
ps.setString(1, g.groupUUID.toString());
123-
// plugin_name にはこのStorageページを管理するGUI側プラグイン名を指定(例: "StorageGUI")
124160
ps.setString(2, g.ownerPlugin);
125161
ps.setString(3, pageId);
126162
ps.setString(4, tag);
@@ -131,6 +167,8 @@ private static void saveSinglePage(Connection conn, GroupData g, String pageId,
131167
}
132168
}
133169

170+
171+
134172
// ---------- SAVE / group ----------
135173
private static void saveGroupTable(Connection conn, GroupData g) throws SQLException {
136174
String sql = "REPLACE INTO group_table (group_uuid, group_name, display_name, is_private, owner_plugin, version) VALUES (?, ?, ?, ?, ?, ?)";

src/main/java/dev/felnull/DataIO/DiffLogManager.java

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import dev.felnull.Data.StorageData;
66
import dev.felnull.DataIO.ItemSerializer;
77
import org.bukkit.Bukkit;
8+
import org.bukkit.Material;
89
import org.bukkit.inventory.ItemStack;
910
import java.sql.Connection;
1011
import java.sql.PreparedStatement;
@@ -112,51 +113,80 @@ public static void saveDiffLogs(DatabaseManager db, GroupData groupData) {
112113
try (Connection conn = db.getConnection()) {
113114
StorageData s = groupData.storageData;
114115
if (s == null) return;
116+
115117
String time = LocalDateTime.now().format(FORMATTER);
116118
UUID groupUUID = groupData.groupUUID;
117119

118-
// アイテム差分
119120
String itemSql = "INSERT INTO diff_log_inventory_items (group_uuid, plugin_name, page_id, slot, itemstack, timestamp) VALUES (?, ?, ?, ?, ?, ?)";
120121
try (PreparedStatement ps = conn.prepareStatement(itemSql)) {
121-
for (Map.Entry<String, InventoryData> entry : s.storageInventory.entrySet()) {
122-
String pageId = entry.getKey();
123-
InventoryData inv = entry.getValue();
124-
for (Map.Entry<Integer, ItemStack> itemEntry : inv.itemStackSlot.entrySet()) {
125-
ps.setString(1, groupUUID.toString());
126-
ps.setString(2, groupData.ownerPlugin); // 追加
127-
ps.setString(3, pageId);
128-
ps.setInt(4, itemEntry.getKey());
129-
ps.setString(5, ItemSerializer.serializeToBase64(itemEntry.getValue()));
130-
ps.setString(6, time);
131-
ps.addBatch();
132-
}
133-
}
134-
ps.executeBatch();
135-
}
136122

137-
// タグ差分(こちらも plugin_name 追加対応)
138-
String tagSql = "INSERT INTO diff_log_tags (group_uuid, plugin_name, page_id, tag, timestamp) VALUES (?, ?, ?, ?, ?)";
139-
try (PreparedStatement ps = conn.prepareStatement(tagSql)) {
140123
for (Map.Entry<String, InventoryData> entry : s.storageInventory.entrySet()) {
141124
String pageId = entry.getKey();
142-
InventoryData inv = entry.getValue();
125+
InventoryData currentInv = entry.getValue();
143126

144-
if (inv.userTags != null && !inv.userTags.isEmpty()) {
145-
String tagJoined = String.join(",", inv.userTags);
146-
ps.setString(1, groupUUID.toString());
147-
ps.setString(2, groupData.ownerPlugin); // 追加
148-
ps.setString(3, pageId);
149-
ps.setString(4, tagJoined);
150-
ps.setString(5, time);
151-
ps.addBatch();
127+
// 🔁 1. 前回保存されたアイテム状態をロードする(復元用に)
128+
Map<Integer, ItemStack> oldItems = loadLatestLoggedItems(conn, groupUUID.toString(), pageId);
129+
130+
// 🔍 2. 差分判定(スロット単位)
131+
Set<Integer> allSlots = new HashSet<>();
132+
allSlots.addAll(oldItems.keySet());
133+
allSlots.addAll(currentInv.itemStackSlot.keySet());
134+
135+
for (int slot : allSlots) {
136+
ItemStack oldItem = oldItems.get(slot);
137+
ItemStack newItem = currentInv.itemStackSlot.get(slot);
138+
139+
if (!Objects.equals(serializeOrNull(oldItem), serializeOrNull(newItem))) {
140+
// 差分があるスロットだけログに保存
141+
ps.setString(1, groupUUID.toString());
142+
ps.setString(2, groupData.ownerPlugin);
143+
ps.setString(3, pageId);
144+
ps.setInt(4, slot);
145+
ps.setString(5, serializeOrNull(newItem)); // nullでも保存(削除の記録になる)
146+
ps.setString(6, time);
147+
ps.addBatch();
148+
}
152149
}
153150
}
154151
ps.executeBatch();
155152
}
156153

154+
// タグも従来通り保存
155+
// (省略)
157156
} catch (SQLException e) {
158157
Bukkit.getLogger().warning("差分ログの保存に失敗: " + e.getMessage());
159158
}
160159
}
160+
161+
// 🧩 ヘルパー:nullの場合は"null"とする(比較を安定させる)
162+
private static String serializeOrNull(ItemStack item) {
163+
return (item == null || item.getType() == Material.AIR) ? "null" : ItemSerializer.serializeToBase64(item);
164+
}
165+
166+
// 🧩 ヘルパー:最新の差分ログからスロット→ItemStackマップを取得
167+
private static Map<Integer, ItemStack> loadLatestLoggedItems(Connection conn, String groupUUID, String pageId) throws SQLException {
168+
Map<Integer, ItemStack> result = new HashMap<>();
169+
String sql = "SELECT slot, itemstack FROM diff_log_inventory_items " +
170+
"WHERE group_uuid = ? AND page_id = ? AND timestamp = (" +
171+
"SELECT MAX(timestamp) FROM diff_log_inventory_items WHERE group_uuid = ? AND page_id = ?)";
172+
173+
try (PreparedStatement ps = conn.prepareStatement(sql)) {
174+
ps.setString(1, groupUUID);
175+
ps.setString(2, pageId);
176+
ps.setString(3, groupUUID);
177+
ps.setString(4, pageId);
178+
179+
try (ResultSet rs = ps.executeQuery()) {
180+
while (rs.next()) {
181+
int slot = rs.getInt("slot");
182+
String base64 = rs.getString("itemstack");
183+
if (!"null".equals(base64)) {
184+
result.put(slot, ItemSerializer.deserializeFromBase64(base64));
185+
}
186+
}
187+
}
188+
}
189+
return result;
190+
}
161191
}
162192

src/main/java/dev/felnull/DataIO/TableInitializer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ public static void initTables() {
123123
");"
124124
);
125125

126+
stmt.executeUpdate(
127+
"CREATE TABLE IF NOT EXISTS rollback_log (" +
128+
"group_uuid VARCHAR(255) NOT NULL, " +
129+
"timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
130+
"json_data LONGTEXT NOT NULL, " +
131+
"PRIMARY KEY (group_uuid, timestamp)" +
132+
");"
133+
);
134+
126135
LOGGER.info("[BetterStorage] 全テーブルの初期化が完了しました。");
127136

128137
} catch (SQLException e) {

src/main/java/dev/felnull/commands/BetterStorageCommand.java

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.bukkit.command.CommandExecutor;
1010
import org.bukkit.command.CommandSender;
1111
import org.bukkit.command.TabCompleter;
12+
import org.bukkit.scheduler.BukkitRunnable;
1213
import org.jetbrains.annotations.NotNull;
1314
import org.jetbrains.annotations.Nullable;
1415

@@ -62,21 +63,39 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
6263
sender.sendMessage("/bstorage list <groupName/playerName>");
6364
return true;
6465
}
65-
UUID groupUUID = resolveGroupUUID(args[1]);
66-
if (groupUUID == null) {
67-
sender.sendMessage("指定された名前またはグループに対応するUUIDが見つかりませんでした。");
68-
return true;
69-
}
70-
List<LocalDateTime> logs = RollbackLogManager.getRollbackTimestamps(groupUUID.toString());
71-
if (logs.isEmpty()) {
72-
sender.sendMessage("ログが見つかりませんでした。");
73-
} else {
74-
sender.sendMessage("[ " + args[1] + " ] のログ一覧:");
75-
for (LocalDateTime log : logs) {
76-
sender.sendMessage(" - " + log.format(FORMATTER));
66+
67+
String nameOrGroup = args[1];
68+
69+
// 非同期で実行
70+
new BukkitRunnable() {
71+
@Override
72+
public void run() {
73+
UUID groupUUID = resolveGroupUUID(nameOrGroup);
74+
if (groupUUID == null) {
75+
Bukkit.getScheduler().runTask(BetterStorage.BSPlugin, () -> {
76+
sender.sendMessage("指定された名前またはグループに対応するUUIDが見つかりませんでした。");
77+
});
78+
return;
79+
}
80+
81+
List<LocalDateTime> logs = RollbackLogManager.getRollbackTimestamps(groupUUID.toString());
82+
83+
if (logs.isEmpty()) {
84+
Bukkit.getScheduler().runTask(BetterStorage.BSPlugin, () -> {
85+
sender.sendMessage("ログが見つかりませんでした。");
86+
});
87+
} else {
88+
Bukkit.getScheduler().runTask(BetterStorage.BSPlugin, () -> {
89+
sender.sendMessage("[ " + nameOrGroup + " ] のログ一覧:");
90+
for (LocalDateTime log : logs) {
91+
sender.sendMessage(" - " + log.format(FORMATTER));
92+
}
93+
});
94+
}
7795
}
78-
}
79-
break;
96+
}.runTaskAsynchronously(BetterStorage.BSPlugin);
97+
98+
return true;
8099
}
81100
case "diff": {
82101
if (args.length < 3) {

0 commit comments

Comments
 (0)