77import dev .felnull .Data .InventoryData ;
88import dev .felnull .Data .StorageData ;
99import org .bukkit .Bukkit ;
10+ import org .bukkit .Material ;
1011import org .bukkit .OfflinePlayer ;
1112import org .bukkit .inventory .ItemStack ;
1213
@@ -74,7 +75,6 @@ public static boolean saveGroupData(GroupData g, long clientVersion) {
7475
7576 // 成功したので差分ログを保存
7677 DiffLogManager .saveDiffLogs (BetterStorage .BSPlugin .getDatabaseManager (), g );
77- Bukkit .getLogger ().info ("保存成功らしい" );
7878 return true ;
7979 } catch (SQLException e ) {
8080 Bukkit .getLogger ().warning ("GroupDataの保存に失敗: " + e .getMessage ());
@@ -96,57 +96,78 @@ private static void saveSinglePage(Connection conn, GroupData g, String pageId,
9696 }
9797
9898 // ---------- 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 = ?" ;
99+ // 🔥 1. 既存スロットとitemstack取得
100+ Map <Integer , String > oldSlotBase64Map = new HashMap <>();
101+ String fetchSql = "SELECT slot, itemstack FROM inventory_item_table WHERE group_uuid = ? AND page_id = ?" ;
103102 try (PreparedStatement ps = conn .prepareStatement (fetchSql )) {
104103 ps .setString (1 , g .groupUUID .toString ());
105104 ps .setString (2 , pageId );
106105 try (ResultSet rs = ps .executeQuery ()) {
107106 while (rs .next ()) {
108- oldSlots . add (rs .getInt ("slot" ));
107+ oldSlotBase64Map . put (rs .getInt ("slot" ), rs . getString ( "itemstack " ));
109108 }
110109 }
111110 }
112111
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 (")" );
112+ Set <Integer > currentSlots = inv .itemStackSlot .keySet ();
123113
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 ();
114+ // 🔥 2. 削除されたスロットを DELETE & ログ記録
115+ Set <Integer > slotsToDelete = new HashSet <>(oldSlotBase64Map .keySet ());
116+ slotsToDelete .removeAll (currentSlots );
117+ if (!slotsToDelete .isEmpty ()) {
118+ StringBuilder sb = new StringBuilder ();
119+ sb .append ("DELETE FROM inventory_item_table WHERE group_uuid = ? AND page_id = ? AND slot IN (" );
120+ sb .append (slotsToDelete .stream ().map (s -> "?" ).collect (Collectors .joining ("," )));
121+ sb .append (")" );
122+ try (PreparedStatement ps = conn .prepareStatement (sb .toString ())) {
123+ ps .setString (1 , g .groupUUID .toString ());
124+ ps .setString (2 , pageId );
125+ int index = 3 ;
126+ for (Integer slot : slotsToDelete ) {
127+ ps .setInt (index ++, slot );
132128 }
129+ ps .executeUpdate ();
130+ }
131+
132+ for (int slot : slotsToDelete ) {
133+ ItemStack dummy = new ItemStack (Material .AIR ); // ログ用にダミー
134+ logInventoryItemChangeAsync (BetterStorage .BSPlugin .getDatabaseManager (),
135+ g .groupUUID , g .ownerPlugin , pageId , slot , OperationType .REMOVE , dummy );
133136 }
134137 }
135138
136- // 🔥 3. 現在のアイテムを REPLACE
139+ // 🔥 3. 追加・更新されたスロットを REPLACE & ログ記録
137140 String itemSql = "REPLACE INTO inventory_item_table (group_uuid, plugin_name, page_id, slot, itemstack, display_name, material, amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" ;
138141 try (PreparedStatement ps = conn .prepareStatement (itemSql )) {
139142 for (Map .Entry <Integer , ItemStack > itemEntry : inv .itemStackSlot .entrySet ()) {
143+ int slot = itemEntry .getKey ();
140144 ItemStack item = itemEntry .getValue ();
145+ String serialized = ItemSerializer .serializeToBase64 (item );
146+
147+ boolean isNew = !oldSlotBase64Map .containsKey (slot );
148+ boolean isChanged = false ;
149+ if (!isNew ) {
150+ String oldBase64 = oldSlotBase64Map .get (slot );
151+ isChanged = !Objects .equals (serialized , oldBase64 );
152+ }
153+
154+ if (!isNew && !isChanged ) continue ; // 差分なし
155+
156+ // DB書き込み
141157 ps .setString (1 , g .groupUUID .toString ());
142158 ps .setString (2 , g .ownerPlugin );
143159 ps .setString (3 , pageId );
144- ps .setInt (4 , itemEntry . getKey () );
145- ps .setString (5 , ItemSerializer . serializeToBase64 ( item ) );
160+ ps .setInt (4 , slot );
161+ ps .setString (5 , serialized );
146162 ps .setString (6 , item .hasItemMeta () ? item .getItemMeta ().getDisplayName () : "" );
147163 ps .setString (7 , item .getType ().name ());
148164 ps .setInt (8 , item .getAmount ());
149165 ps .addBatch ();
166+
167+ // ログ
168+ logInventoryItemChangeAsync (BetterStorage .BSPlugin .getDatabaseManager (),
169+ g .groupUUID , g .ownerPlugin , pageId , slot ,
170+ isNew ? OperationType .ADD : OperationType .UPDATE , item );
150171 }
151172 ps .executeBatch ();
152173 }
@@ -656,7 +677,41 @@ public static void deleteGroupData(Connection conn, UUID groupUUID, String plugi
656677 // ===== 4. LOG =============================================
657678 // ===========================================================
658679
659- public static void logInventoryItemChange (Connection conn , UUID groupUUID , String pluginName , String pageId , int slot , String op , ItemStack item ) throws SQLException {
680+ public static void logInventoryItemChangeAsync (DatabaseManager db , UUID groupUUID , String pluginName , String pageId , int slot , OperationType op , ItemStack item ) {
681+ // 削除操作ならAIRでも保存する
682+ boolean isRemove = (op == OperationType .REMOVE );
683+
684+ // nullやAIRを除外(ただし削除は除外しない)
685+ if (item == null || (!isRemove && item .getType () == Material .AIR )) return ;
686+
687+ String serializedItem = ItemSerializer .serializeToBase64 (item );
688+ String displayName = item .hasItemMeta () && item .getItemMeta ().hasDisplayName () ? item .getItemMeta ().getDisplayName () : "" ;
689+ String material = item .getType ().name ();
690+ int amount = item .getAmount ();
691+
692+ Bukkit .getScheduler ().runTaskAsynchronously (BetterStorage .BSPlugin , () -> {
693+ try (Connection conn = db .getConnection ()) {
694+ try (PreparedStatement ps = conn .prepareStatement (
695+ "INSERT INTO inventory_item_log (group_uuid, plugin_name, page_id, slot, operation_type, itemstack, display_name, material, amount, timestamp) " +
696+ "VALUES (?,?,?,?,?,?,?,?,?,NOW())" )) {
697+ ps .setString (1 , groupUUID .toString ());
698+ ps .setString (2 , pluginName );
699+ ps .setString (3 , pageId );
700+ ps .setInt (4 , slot );
701+ ps .setString (5 , op .toDbString ());
702+ ps .setString (6 , serializedItem );
703+ ps .setString (7 , displayName );
704+ ps .setString (8 , material );
705+ ps .setInt (9 , amount );
706+ ps .executeUpdate ();
707+ }
708+ } catch (SQLException e ) {
709+ Bukkit .getLogger ().warning ("[BetterStorage] 非同期ログ保存失敗: " + e .getMessage ());
710+ }
711+ });
712+ }
713+
714+ private static void logInventoryItemChange (Connection conn , UUID groupUUID , String pluginName , String pageId , int slot , String op , ItemStack item ) throws SQLException {
660715 try (PreparedStatement ps = conn .prepareStatement (
661716 "INSERT INTO inventory_item_log (group_uuid, plugin_name, page_id, slot, operation_type, itemstack, display_name, material, amount, timestamp) VALUES (?,?,?,?,?,?,?,?,?,NOW())" )) {
662717 ps .setString (1 , groupUUID .toString ());
0 commit comments