11package org .mvplugins .multiverse .inventories .commands .bulkedit .playerprofile ;
22
3+ import com .google .common .io .Files ;
34import org .jvnet .hk2 .annotations .Service ;
45import org .mvplugins .multiverse .core .command .MVCommandIssuer ;
56import org .mvplugins .multiverse .core .command .queue .CommandQueueManager ;
1213import org .mvplugins .multiverse .inventories .commands .InventoriesCommand ;
1314import org .mvplugins .multiverse .inventories .config .InventoriesConfig ;
1415import org .mvplugins .multiverse .inventories .profile .GlobalProfile ;
16+ import org .mvplugins .multiverse .inventories .profile .ProfileCacheManager ;
1517import org .mvplugins .multiverse .inventories .profile .ProfileDataSource ;
1618import org .mvplugins .multiverse .inventories .profile .key .GlobalProfileKey ;
1719import org .mvplugins .multiverse .inventories .profile .key .ProfileKey ;
20+ import org .mvplugins .multiverse .inventories .profile .key .ProfileType ;
1821import org .mvplugins .multiverse .inventories .profile .key .ProfileTypes ;
1922import org .mvplugins .multiverse .inventories .profile .key .ContainerType ;
23+ import org .mvplugins .multiverse .inventories .profile .ProfileFilesLocator ;
24+ import org .mvplugins .multiverse .inventories .util .ItemStackConverter ;
2025
21- import java .util .Arrays ;
26+ import java .io .File ;
27+ import java .nio .charset .StandardCharsets ;
28+ import java .util .ArrayList ;
29+ import java .util .HashMap ;
30+ import java .util .HashSet ;
31+ import java .util .List ;
32+ import java .util .Map ;
33+ import java .util .Set ;
34+ import java .util .UUID ;
2235import java .util .concurrent .CompletableFuture ;
2336import java .util .concurrent .atomic .AtomicLong ;
2437
@@ -28,21 +41,32 @@ final class MigrateInventorySerializationCommand extends InventoriesCommand {
2841 private final CommandQueueManager commandQueueManager ;
2942 private final ProfileDataSource profileDataSource ;
3043 private final InventoriesConfig inventoriesConfig ;
44+ private final ProfileCacheManager profileCacheManager ;
45+ private final ProfileFilesLocator profileFilesLocator ;
3146
3247 @ Inject
3348 MigrateInventorySerializationCommand (
3449 @ NotNull CommandQueueManager commandQueueManager ,
3550 @ NotNull ProfileDataSource profileDataSource ,
36- @ NotNull InventoriesConfig inventoriesConfig
51+ @ NotNull InventoriesConfig inventoriesConfig ,
52+ @ NotNull ProfileCacheManager profileCacheManager ,
53+ @ NotNull ProfileFilesLocator profileFilesLocator
3754 ) {
3855 this .commandQueueManager = commandQueueManager ;
3956 this .profileDataSource = profileDataSource ;
4057 this .inventoriesConfig = inventoriesConfig ;
58+ this .profileCacheManager = profileCacheManager ;
59+ this .profileFilesLocator = profileFilesLocator ;
4160 }
4261
4362 @ Subcommand ("bulkedit migrate inventory-serialization nbt" )
4463 @ CommandPermission ("multiverse.inventories.bulkedit" )
4564 void onNbtCommand (MVCommandIssuer issuer ) {
65+ if (!ItemStackConverter .hasByteSerializeSupport ()) {
66+ issuer .sendMessage ("NBT serialization is only supported on PaperMC 1.20.2 or higher!" );
67+ issuer .sendMessage ("Conversion to NBT is not possible on your current server version." );
68+ return ;
69+ }
4670 commandQueueManager .addToQueue (CommandQueuePayload .issuer (issuer )
4771 .prompt (Message .of ("Are you sure you want to migrate all player data to NBT?" ))
4872 .action (() -> doMigration (issuer , true )));
@@ -57,44 +81,114 @@ void onBukkitCommand(MVCommandIssuer issuer) {
5781 }
5882
5983 private void doMigration (MVCommandIssuer issuer , boolean useByteSerialization ) {
84+ issuer .sendMessage ("Updating config and clearing caches..." );
6085 inventoriesConfig .setUseByteSerializationForInventoryData (useByteSerialization );
6186 inventoriesConfig .save ();
87+ profileCacheManager .clearAllCache ();
6288
6389 long startTime = System .nanoTime ();
6490 AtomicLong profileCounter = new AtomicLong (0 );
65- CompletableFuture .allOf (profileDataSource .listGlobalProfileUUIDs ()
66- .stream ()
67- .map (playerUUID -> profileDataSource .getGlobalProfile (GlobalProfileKey .of (playerUUID , "" ))
68- .thenCompose (profile -> run (profile , profileCounter ))
69- .exceptionally (throwable -> {
70- issuer .sendMessage ("Error updating player " + playerUUID + ": " + throwable .getMessage ());
71- return null ;
72- }))
73- .toArray (CompletableFuture []::new ))
91+
92+ Map <ContainerType , List <String >> containerNames = new HashMap <>();
93+ for (ContainerType type : ContainerType .values ()) {
94+ containerNames .put (type , profileDataSource .listContainerDataNames (type ));
95+ }
96+
97+ Set <String > playerIdentifiers = new HashSet <>();
98+ // Scan global files
99+ profileFilesLocator .listGlobalFiles ().forEach (file ->
100+ playerIdentifiers .add (Files .getNameWithoutExtension (file .getName ())));
101+
102+ // Scan world and group files
103+ for (ContainerType type : ContainerType .values ()) {
104+ for (File folder : profileFilesLocator .listProfileContainerFolders (type )) {
105+ profileFilesLocator .listPlayerProfileFiles (type , folder .getName ()).forEach (file ->
106+ playerIdentifiers .add (Files .getNameWithoutExtension (file .getName ())));
107+ }
108+ }
109+
110+ issuer .sendMessage ("Found " + playerIdentifiers .size () + " unique player identifiers to migrate." );
111+
112+ migrateNextPlayer (issuer , new ArrayList <>(playerIdentifiers ), 0 , containerNames , profileCounter )
74113 .thenRun (() -> {
75114 long timeDuration = (System .nanoTime () - startTime ) / 1000000 ;
76115 issuer .sendMessage ("Updated " + profileCounter .get () + " player profiles." );
77116 issuer .sendMessage ("Bulk edit completed in " + timeDuration + " ms." );
78117 });
79118 }
80119
81- private CompletableFuture <Void > run (GlobalProfile profile , AtomicLong profileCounter ) {
82- return CompletableFuture .allOf (Arrays .stream (ContainerType .values ())
83- .flatMap (containerType -> profileDataSource .listContainerDataNames (containerType ).stream ()
84- .flatMap (dataName -> ProfileTypes .getTypes ().stream ()
85- .map (profileType -> profileDataSource .getPlayerProfile (ProfileKey .of (
86- containerType ,
87- dataName ,
88- profileType ,
89- profile .getPlayerUUID (),
90- profile .getLastKnownName ()
91- )).thenCompose (playerProfile -> {
92- if (playerProfile .getData ().isEmpty ()) {
93- return CompletableFuture .completedFuture (null );
94- }
95- profileCounter .incrementAndGet ();
96- return profileDataSource .updatePlayerProfile (playerProfile );
97- }))))
98- .toArray (CompletableFuture []::new ));
120+ private CompletableFuture <Void > migrateNextPlayer (
121+ MVCommandIssuer issuer ,
122+ List <String > playerIdentifiers ,
123+ int index ,
124+ Map <ContainerType , List <String >> containerNames ,
125+ AtomicLong profileCounter
126+ ) {
127+ if (index >= playerIdentifiers .size ()) {
128+ return CompletableFuture .completedFuture (null );
129+ }
130+
131+ String playerIdentifier = playerIdentifiers .get (index );
132+ UUID playerUUID ;
133+ try {
134+ playerUUID = UUID .fromString (playerIdentifier );
135+ } catch (IllegalArgumentException e ) {
136+ playerUUID = UUID .nameUUIDFromBytes (("OfflinePlayer:" + playerIdentifier ).getBytes (StandardCharsets .UTF_8 ));
137+ }
138+
139+ if (index % 50 == 0 && index > 0 ) {
140+ issuer .sendMessage ("Processed " + index + " players..." );
141+ }
142+
143+ return profileDataSource .getGlobalProfile (GlobalProfileKey .of (playerUUID , playerIdentifier ))
144+ .thenCompose (profile -> run (issuer , profile , containerNames , profileCounter ))
145+ .exceptionally (throwable -> {
146+ issuer .sendMessage ("Error updating player " + playerIdentifier + ": " + throwable .getMessage ());
147+ return null ;
148+ })
149+ .thenCompose (v -> migrateNextPlayer (issuer , playerIdentifiers , index + 1 , containerNames , profileCounter ));
150+ }
151+
152+ private CompletableFuture <Void > run (
153+ MVCommandIssuer issuer ,
154+ GlobalProfile profile ,
155+ Map <ContainerType , List <String >> containerNames ,
156+ AtomicLong profileCounter
157+ ) {
158+ String playerName = profile .getLastKnownName ();
159+ if (playerName == null || playerName .isEmpty ()) {
160+ playerName = profile .getPlayerUUID ().toString ();
161+ }
162+
163+ CompletableFuture <Void > future = CompletableFuture .completedFuture (null );
164+ for (ContainerType containerType : ContainerType .values ()) {
165+ List <String > dataNames = containerNames .get (containerType );
166+ for (String dataName : dataNames ) {
167+ for (ProfileType profileType : ProfileTypes .getTypes ()) {
168+ ProfileKey profileKey = ProfileKey .of (
169+ containerType ,
170+ dataName ,
171+ profileType ,
172+ profile .getPlayerUUID (),
173+ playerName
174+ );
175+ future = future .thenCompose (v -> profileDataSource .getPlayerProfile (profileKey )
176+ .thenCompose (playerProfile -> {
177+ if (playerProfile .getData ().isEmpty ()) {
178+ return CompletableFuture .completedFuture (null );
179+ }
180+ profileCounter .incrementAndGet ();
181+ return profileDataSource .updatePlayerProfile (playerProfile );
182+ })
183+ .exceptionally (throwable -> {
184+ issuer .sendMessage (String .format ("Error migrating profile %s %s/%s for player %s: %s" ,
185+ containerType , dataName , profileType , profile .getPlayerUUID (), throwable .getMessage ()));
186+ return null ;
187+ })
188+ );
189+ }
190+ }
191+ }
192+ return future ;
99193 }
100194}
0 commit comments