Browse Source

I'm working on DatabaseEditor.

master
Stanislav Usenkov 9 years ago
parent
commit
eb298d12cf
  1. 2
      pom.xml
  2. 2
      src/main/java/ru/simsonic/rscPermissions/API/RowEntity.java
  3. 2
      src/main/java/ru/simsonic/rscPermissions/API/RowInheritance.java
  4. 2
      src/main/java/ru/simsonic/rscPermissions/API/RowPermission.java
  5. 17
      src/main/java/ru/simsonic/rscPermissions/BridgeForBukkitAPI.java
  6. 4
      src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandGroup.java
  7. 4
      src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandUser.java
  8. 11
      src/main/java/ru/simsonic/rscPermissions/Engine/Backends/BackendDatabase.java
  9. 1
      src/main/java/ru/simsonic/rscPermissions/Engine/Backends/BackendJson.java
  10. 11
      src/main/java/ru/simsonic/rscPermissions/Engine/Backends/DatabaseAction.java
  11. 14
      src/main/java/ru/simsonic/rscPermissions/Engine/Backends/DatabaseContents.java
  12. 74
      src/main/java/ru/simsonic/rscPermissions/Engine/Backends/DatabaseEditor.java
  13. 6
      src/main/java/ru/simsonic/rscPermissions/Engine/InternalCache.java
  14. 2
      src/main/resources/languages/russian.yml

2
pom.xml

@ -4,7 +4,7 @@
<groupId>ru.simsonic</groupId> <groupId>ru.simsonic</groupId>
<artifactId>rscPermissions</artifactId> <artifactId>rscPermissions</artifactId>
<version>0.10.2b-SNAPSHOT</version> <version>0.10.3b-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>rscPermissions</name> <name>rscPermissions</name>

2
src/main/java/ru/simsonic/rscPermissions/API/RowEntity.java

@ -5,12 +5,12 @@ import java.sql.Timestamp;
public class RowEntity implements Cloneable, Comparable<RowEntity> public class RowEntity implements Cloneable, Comparable<RowEntity>
{ {
public int id; public int id;
public String splittedId;
public String entity; public String entity;
public EntityType entityType; public EntityType entityType;
public String prefix; public String prefix;
public String suffix; public String suffix;
public Timestamp lifetime; public Timestamp lifetime;
public transient String splittedId;
public transient PlayerType playerType; public transient PlayerType playerType;
public transient RowPermission[] permissions; public transient RowPermission[] permissions;
public transient RowInheritance[] inheritance; public transient RowInheritance[] inheritance;

2
src/main/java/ru/simsonic/rscPermissions/API/RowInheritance.java

@ -7,7 +7,6 @@ import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes;
public class RowInheritance implements Cloneable, Comparable<RowInheritance> public class RowInheritance implements Cloneable, Comparable<RowInheritance>
{ {
public int id; public int id;
public String splittedId;
public String entity; public String entity;
public String parent; public String parent;
public String instance; public String instance;
@ -16,6 +15,7 @@ public class RowInheritance implements Cloneable, Comparable<RowInheritance>
public Destination destination; public Destination destination;
public int expirience; public int expirience;
public Timestamp lifetime; public Timestamp lifetime;
public transient String splittedId;
public transient PlayerType playerType; public transient PlayerType playerType;
public transient String destinationSource; public transient String destinationSource;
public transient RowEntity entityChild; public transient RowEntity entityChild;

2
src/main/java/ru/simsonic/rscPermissions/API/RowPermission.java

@ -5,7 +5,6 @@ import java.sql.Timestamp;
public class RowPermission implements Cloneable public class RowPermission implements Cloneable
{ {
public int id; public int id;
public String splittedId;
public String entity; public String entity;
public EntityType entityType; public EntityType entityType;
public String permission; public String permission;
@ -13,6 +12,7 @@ public class RowPermission implements Cloneable
public Destination destination; public Destination destination;
public int expirience; public int expirience;
public Timestamp lifetime; public Timestamp lifetime;
public transient String splittedId;
public transient PlayerType playerType; public transient PlayerType playerType;
public transient String destinationSource; public transient String destinationSource;
public transient RowEntity entityObject; public transient RowEntity entityObject;

17
src/main/java/ru/simsonic/rscPermissions/BridgeForBukkitAPI.java

@ -1,9 +1,12 @@
package ru.simsonic.rscPermissions; package ru.simsonic.rscPermissions;
import com.sk89q.wepif.PermissionsResolverManager; import com.sk89q.wepif.PermissionsResolverManager;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes; import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes;
import ru.simsonic.rscPermissions.API.Settings; import ru.simsonic.rscPermissions.API.Settings;
import ru.simsonic.rscPermissions.Bukkit.VaultChat; import ru.simsonic.rscPermissions.Bukkit.VaultChat;
@ -28,15 +31,15 @@ public class BridgeForBukkitAPI
this.vaultPermission = new VaultPermission(this); this.vaultPermission = new VaultPermission(this);
this.vaultChat = new VaultChat(this, vaultPermission); this.vaultChat = new VaultChat(this, vaultPermission);
} }
public org.bukkit.plugin.java.JavaPlugin getPlugin() public JavaPlugin getPlugin()
{ {
return this.rscp; return this.rscp;
} }
public net.milkbowl.vault.permission.Permission getPermission() public Permission getPermission()
{ {
return this.vaultPermission; return this.vaultPermission;
} }
public net.milkbowl.vault.chat.Chat getChat() public Chat getChat()
{ {
return this.vaultChat; return this.vaultChat;
} }
@ -62,9 +65,9 @@ public class BridgeForBukkitAPI
rscp.getServer().getServicesManager().register( rscp.getServer().getServicesManager().register(
net.milkbowl.vault.permission.Permission.class, vaultPermission, net.milkbowl.vault.permission.Permission.class, vaultPermission,
rscp, ServicePriority.Highest); rscp, ServicePriority.Highest);
console.sendMessage(GenericChatCodes.processStringStatic("[rscp] " + Phrases.INTEGRATION_V_Y.toString())); console.sendMessage(GenericChatCodes.processStringStatic(Settings.CHAT_PREFIX + Phrases.INTEGRATION_V_Y.toString()));
} else } else
console.sendMessage(GenericChatCodes.processStringStatic("[rscp] " + Phrases.INTEGRATION_V_N.toString())); console.sendMessage(GenericChatCodes.processStringStatic(Settings.CHAT_PREFIX + Phrases.INTEGRATION_V_N.toString()));
} }
protected void setupWEPIF() protected void setupWEPIF()
{ {
@ -78,9 +81,9 @@ public class BridgeForBukkitAPI
prm.setPluginPermissionsResolver(wepif); prm.setPluginPermissionsResolver(wepif);
else else
PermissionsResolverManager.initialize(wepif); PermissionsResolverManager.initialize(wepif);
console.sendMessage(GenericChatCodes.processStringStatic("[rscp] " + Phrases.INTEGRATION_WE_Y.toString())); console.sendMessage(GenericChatCodes.processStringStatic(Settings.CHAT_PREFIX + Phrases.INTEGRATION_WE_Y.toString()));
} else } else
console.sendMessage(GenericChatCodes.processStringStatic("[rscp] " + Phrases.INTEGRATION_WE_N.toString())); console.sendMessage(GenericChatCodes.processStringStatic(Settings.CHAT_PREFIX + Phrases.INTEGRATION_WE_N.toString()));
} }
public void printDebugString(String info) public void printDebugString(String info)
{ {

4
src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandGroup.java

@ -4,7 +4,7 @@ import java.util.ArrayList;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException; import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException;
import ru.simsonic.rscPermissions.BukkitPluginMain; import ru.simsonic.rscPermissions.BukkitPluginMain;
import ru.simsonic.rscPermissions.Engine.Backends.DatabaseTransaction; import ru.simsonic.rscPermissions.Engine.Backends.DatabaseEditor;
import ru.simsonic.rscPermissions.Engine.ResolutionResult; import ru.simsonic.rscPermissions.Engine.ResolutionResult;
public class CommandGroup public class CommandGroup
@ -112,7 +112,7 @@ public class CommandGroup
private void addGroup(ResolutionResult result, String group, String parent, String destination, Integer seconds) throws CommandAnswerException private void addGroup(ResolutionResult result, String group, String parent, String destination, Integer seconds) throws CommandAnswerException
{ {
final ArrayList<String> answer = new ArrayList<>(); final ArrayList<String> answer = new ArrayList<>();
final DatabaseTransaction databaseTransaction = new DatabaseTransaction(rscp); final DatabaseEditor databaseTransaction = new DatabaseEditor(rscp);
databaseTransaction.apply(); databaseTransaction.apply();
throw new CommandAnswerException(answer); throw new CommandAnswerException(answer);
} }

4
src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandUser.java

@ -7,7 +7,7 @@ import org.bukkit.entity.Player;
import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException; import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException;
import ru.simsonic.rscPermissions.Bukkit.BukkitUtilities; import ru.simsonic.rscPermissions.Bukkit.BukkitUtilities;
import ru.simsonic.rscPermissions.BukkitPluginMain; import ru.simsonic.rscPermissions.BukkitPluginMain;
import ru.simsonic.rscPermissions.Engine.Backends.DatabaseTransaction; import ru.simsonic.rscPermissions.Engine.Backends.DatabaseEditor;
import ru.simsonic.rscPermissions.Engine.Matchers; import ru.simsonic.rscPermissions.Engine.Matchers;
import ru.simsonic.rscPermissions.Engine.ResolutionResult; import ru.simsonic.rscPermissions.Engine.ResolutionResult;
@ -112,7 +112,7 @@ public class CommandUser
private void addGroup(ResolutionResult result, String user, String parent, String destination, Integer seconds) throws CommandAnswerException private void addGroup(ResolutionResult result, String user, String parent, String destination, Integer seconds) throws CommandAnswerException
{ {
final ArrayList<String> answer = new ArrayList<>(); final ArrayList<String> answer = new ArrayList<>();
final DatabaseTransaction databaseTransaction = new DatabaseTransaction(rscp); final DatabaseEditor databaseTransaction = new DatabaseEditor(rscp);
databaseTransaction.apply(); databaseTransaction.apply();
throw new CommandAnswerException(answer); throw new CommandAnswerException(answer);
} }

11
src/main/java/ru/simsonic/rscPermissions/Engine/Backends/BackendDatabase.java

@ -31,11 +31,12 @@ public class BackendDatabase extends ConnectionMySQL
public synchronized DatabaseContents retrieveContents() public synchronized DatabaseContents retrieveContents()
{ {
executeUpdateT("Cleanup"); executeUpdateT("Cleanup");
final DatabaseContents contents = new DatabaseContents(); final DatabaseContents result = new DatabaseContents();
contents.entities = fetchEntities(); result.entities = fetchEntities();
contents.permissions = fetchPermissions(); result.permissions = fetchPermissions();
contents.inheritance = fetchInheritance(); result.inheritance = fetchInheritance();
return contents; result.cached = true;
return result;
} }
private RowEntity[] fetchEntities() private RowEntity[] fetchEntities()
{ {

1
src/main/java/ru/simsonic/rscPermissions/Engine/Backends/BackendJson.java

@ -49,6 +49,7 @@ public class BackendJson
result.inheritance = gson.fromJson(jr, RowInheritance[].class); result.inheritance = gson.fromJson(jr, RowInheritance[].class);
} catch(IOException ex) { } catch(IOException ex) {
} }
result.cached = true;
return result; return result;
} }
public synchronized void saveContents(DatabaseContents contents) public synchronized void saveContents(DatabaseContents contents)

11
src/main/java/ru/simsonic/rscPermissions/Engine/Backends/DatabaseAction.java

@ -1,11 +0,0 @@
package ru.simsonic.rscPermissions.Engine.Backends;
import ru.simsonic.rscPermissions.API.RowInheritance;
import ru.simsonic.rscPermissions.API.RowPermission;
public class DatabaseAction
{
public int id;
public RowPermission permissions;
public RowInheritance inheritance;
}

14
src/main/java/ru/simsonic/rscPermissions/Engine/Backends/DatabaseContents.java

@ -12,6 +12,7 @@ import ru.simsonic.rscPermissions.Engine.Matchers;
public class DatabaseContents public class DatabaseContents
{ {
public boolean cached;
public RowEntity entities[]; public RowEntity entities[];
public RowPermission permissions[]; public RowPermission permissions[];
public RowInheritance inheritance[]; public RowInheritance inheritance[];
@ -28,24 +29,25 @@ public class DatabaseContents
final ArrayList<RowInheritance> li = new ArrayList<>(); final ArrayList<RowInheritance> li = new ArrayList<>();
try try
{ {
long subRowEntry;
// Entities // Entities
long subRowEntry = 0;
for(RowEntity row : entities) for(RowEntity row : entities)
{ {
subRowEntry = 0;
final String[] splittedByEntity = Matchers.genericParse(row.entity); final String[] splittedByEntity = Matchers.genericParse(row.entity);
for(String oneEntity : splittedByEntity) for(String oneEntity : splittedByEntity)
{ {
final RowEntity clone = row.clone(); final RowEntity clone = row.clone();
clone.splittedId = String.format("%d%s%d", row.id, Settings.SPLITTED_ID_SEP, subRowEntry); clone.splittedId = String.format("e%d%s%d", row.id, Settings.SPLITTED_ID_SEP, subRowEntry);
clone.entity = PlayerType.normalize(oneEntity); clone.entity = PlayerType.normalize(oneEntity);
le.add(clone); le.add(clone);
subRowEntry += 1; subRowEntry += 1;
} }
} }
// Permissions // Permissions
subRowEntry = 0;
for(RowPermission row : permissions) for(RowPermission row : permissions)
{ {
subRowEntry = 0;
final String[] splittedByEntity = Matchers.genericParse(row.entity); final String[] splittedByEntity = Matchers.genericParse(row.entity);
final String[] splittedByPermission = Matchers.genericParse(row.permission); final String[] splittedByPermission = Matchers.genericParse(row.permission);
final String[] splittedByDestination = Matchers.genericParse(row.destinationSource); final String[] splittedByDestination = Matchers.genericParse(row.destinationSource);
@ -57,7 +59,7 @@ public class DatabaseContents
for(String entity : splittedByEntity) for(String entity : splittedByEntity)
{ {
final RowPermission clone = row.clone(); final RowPermission clone = row.clone();
clone.splittedId = String.format("%d%s%d", row.id, Settings.SPLITTED_ID_SEP, subRowEntry); clone.splittedId = String.format("p%d%s%d", row.id, Settings.SPLITTED_ID_SEP, subRowEntry);
clone.entity = PlayerType.normalize(entity); clone.entity = PlayerType.normalize(entity);
clone.permission = permission; clone.permission = permission;
clone.destination = destination; clone.destination = destination;
@ -67,9 +69,9 @@ public class DatabaseContents
} }
} }
// Inheritance // Inheritance
subRowEntry = 0;
for(RowInheritance row : inheritance) for(RowInheritance row : inheritance)
{ {
subRowEntry = 0;
final String[] splittedByEntity = Matchers.genericParse(row.entity); final String[] splittedByEntity = Matchers.genericParse(row.entity);
final String[] splittedByParent = Matchers.genericParse(row.parent); final String[] splittedByParent = Matchers.genericParse(row.parent);
final String[] splittedByDestination = Matchers.genericParse(row.destinationSource); final String[] splittedByDestination = Matchers.genericParse(row.destinationSource);
@ -81,7 +83,7 @@ public class DatabaseContents
for(String entity : splittedByEntity) for(String entity : splittedByEntity)
{ {
final RowInheritance clone = row.clone(); final RowInheritance clone = row.clone();
clone.splittedId = String.format("%d%s%d", row.id, Settings.SPLITTED_ID_SEP, subRowEntry); clone.splittedId = String.format("i%d%s%d", row.id, Settings.SPLITTED_ID_SEP, subRowEntry);
clone.entity = PlayerType.normalize(entity); clone.entity = PlayerType.normalize(entity);
clone.parent = parent; clone.parent = parent;
clone.deriveInstance(); clone.deriveInstance();

74
src/main/java/ru/simsonic/rscPermissions/Engine/Backends/DatabaseTransaction.java → src/main/java/ru/simsonic/rscPermissions/Engine/Backends/DatabaseEditor.java

@ -1,29 +1,63 @@
package ru.simsonic.rscPermissions.Engine.Backends; package ru.simsonic.rscPermissions.Engine.Backends;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException; import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException;
import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes; import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes;
import ru.simsonic.rscPermissions.API.RowEntity;
import ru.simsonic.rscPermissions.API.RowInheritance; import ru.simsonic.rscPermissions.API.RowInheritance;
import ru.simsonic.rscPermissions.API.RowPermission; import ru.simsonic.rscPermissions.API.RowPermission;
import ru.simsonic.rscPermissions.BukkitPluginMain; import ru.simsonic.rscPermissions.BukkitPluginMain;
public class DatabaseTransaction public class DatabaseEditor
{ {
private final BukkitPluginMain rscp; private final BukkitPluginMain plugin;
private final List<DatabaseAction> actions = new LinkedList<>(); private final Map<String, RowEntity> entities = new HashMap<>();
public DatabaseTransaction(BukkitPluginMain rscp) private final Map<String, RowPermission> permissions = new HashMap<>();
private final Map<String, RowInheritance> inheritance = new HashMap<>();
public DatabaseEditor(BukkitPluginMain rscp)
{ {
this.rscp = rscp; this.plugin = rscp;
}
public void fill(DatabaseContents contents)
{
clear();
for(RowEntity row : contents.entities)
entities.put(row.splittedId, row);
for(RowPermission row : contents.permissions)
permissions.put(row.splittedId, row);
for(RowInheritance row : contents.inheritance)
inheritance.put(row.splittedId, row);
}
private void clear()
{
entities.clear();
permissions.clear();
inheritance.clear();
}
public void removeEntity(String splittedId)
{
// Do I know something about such row?
final RowEntity row = entities.get(splittedId);
if(row == null)
return;
// Find if it is part of a multidata row
final List<RowEntity> fromSameRow = new LinkedList<>();
for(RowEntity test : entities.values())
if(test != row && test.id == row.id)
fromSameRow.add(test);
// TO DO : REMOVE ENTITY ROW HERE
// Restore all data that contained in that row
if(!fromSameRow.isEmpty())
{
// DO RESTORE
}
} }
/*
What can happen?
<user> add permission [destination] [lifetime]
<user> remove permission [destination]
<user> add group [destination] [lifitime]
<user> remove group [destination]
*/
public void apply() throws CommandAnswerException public void apply() throws CommandAnswerException
{ {
final DatabaseContents contents = prepareChanges(); final DatabaseContents contents = prepareChanges();
@ -61,22 +95,22 @@ public class DatabaseTransaction
private DatabaseContents prepareChanges() private DatabaseContents prepareChanges()
{ {
// START TRANSACTION AND LOCK TABLE // START TRANSACTION AND LOCK TABLE
rscp.connection.lockTableEntities(); plugin.connection.lockTableEntities();
rscp.connection.lockTablePermissions(); plugin.connection.lockTablePermissions();
rscp.connection.lockTableInheritance(); plugin.connection.lockTableInheritance();
rscp.connection.transactionStart(); plugin.connection.transactionStart();
// SELECT FROM DATABASE INTO LOCAL CACHE TO MAKE IT ACTUAL // SELECT FROM DATABASE INTO LOCAL CACHE TO MAKE IT ACTUAL
return rscp.commandHelper.threadFetchDatabaseContents.remoteToLocal(); return plugin.commandHelper.threadFetchDatabaseContents.remoteToLocal();
} }
private void finishChanges(boolean commit) private void finishChanges(boolean commit)
{ {
// COMMIT OR ROLLBACK ACTIONS // COMMIT OR ROLLBACK ACTIONS
if(commit) if(commit)
rscp.connection.transactionCommit(); plugin.connection.transactionCommit();
else else
rscp.connection.transactionCommit(); plugin.connection.transactionCommit();
// CALL PLUGIN TO APPLY ALL THIS CHANGES // CALL PLUGIN TO APPLY ALL THIS CHANGES
rscp.commandHelper.threadFetchDatabaseContents.run(); plugin.commandHelper.threadFetchDatabaseContents.run();
} }
private RowPermission restorePermissionsAfterDelete(DatabaseContents contents, RowPermission remove) private RowPermission restorePermissionsAfterDelete(DatabaseContents contents, RowPermission remove)
{ {

6
src/main/java/ru/simsonic/rscPermissions/Engine/InternalCache.java

@ -27,6 +27,7 @@ public class InternalCache
private boolean groupsInheritParentPrefixes = true; private boolean groupsInheritParentPrefixes = true;
private RowEntity implicit_g; private RowEntity implicit_g;
private RowEntity implicit_u; private RowEntity implicit_u;
private boolean freshRemoteData;
public void setDefaultGroup(String defaultGroup, boolean alwaysInheritDefaultGroup, boolean groupsInheritParentPrefixes) public void setDefaultGroup(String defaultGroup, boolean alwaysInheritDefaultGroup, boolean groupsInheritParentPrefixes)
{ {
defaultInheritance.parent = defaultGroup; defaultInheritance.parent = defaultGroup;
@ -37,12 +38,17 @@ public class InternalCache
public synchronized void fill(DatabaseContents contents) public synchronized void fill(DatabaseContents contents)
{ {
clear(); clear();
this.freshRemoteData = contents.cached;
importEntities (contents); importEntities (contents);
importPermissions(contents.permissions); importPermissions(contents.permissions);
importInheritance(contents.inheritance); importInheritance(contents.inheritance);
implicit_g = entities_g.get(""); implicit_g = entities_g.get("");
implicit_u = entities_u.get(""); implicit_u = entities_u.get("");
} }
public boolean isFreshData()
{
return freshRemoteData;
}
private void importEntities(DatabaseContents contents) private void importEntities(DatabaseContents contents)
{ {
final HashSet<String> names_u = new HashSet<>(); final HashSet<String> names_u = new HashSet<>();

2
src/main/resources/languages/russian.yml

@ -2,7 +2,7 @@ generic:
enabled: "[rscp] Плагин rscPermissions успешно включён." enabled: "[rscp] Плагин rscPermissions успешно включён."
disabled: "[rscp] Плагин rscPermissions выключен." disabled: "[rscp] Плагин rscPermissions выключен."
reloaded: "[rscp] Плагин rscPermissions перезапущен, конфигурация перечитана." reloaded: "[rscp] Плагин rscPermissions перезапущен, конфигурация перечитана."
metrics: "[rscp] Включён сбор метрики (mcstats.org)." metrics: "[rscp] Включён сбор статистической информации (mcstats.org)."
player-only: "{_LR}Эта команда не может быть использована из консоли." player-only: "{_LR}Эта команда не может быть использована из консоли."
console-only: "{_LR}Эта команда может быть использована только из консоли." console-only: "{_LR}Эта команда может быть использована только из консоли."
server-is-full: "{_LR}Сервер заполнен, оставшиеся слоты находятся в резерве." server-is-full: "{_LR}Сервер заполнен, оставшиеся слоты находятся в резерве."

Loading…
Cancel
Save