mirror of
https://github.com/TotalFreedomMC/TF-EssentialsX.git
synced 2025-02-12 04:20:41 +00:00
Optimizations include changing the return of comparePermissionString.
This commit is contained in:
parent
0cb77d8a03
commit
b25a8f059b
6 changed files with 282 additions and 260 deletions
|
@ -521,10 +521,10 @@ public class NijikoPermissionsProxy extends PermissionHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean has(String world, String playerName, String permission) {
|
public boolean has(String world, String playerName, String permission) {
|
||||||
if (permission == null || permission.equals("")) {
|
if (permission == null || permission.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (playerName == null || playerName == "") {
|
if (playerName == null || playerName.isEmpty()) {
|
||||||
GroupManager.logger.severe("A plugin is asking permission '" + permission + "' for a null player... Which plugin does that? Bastards!");
|
GroupManager.logger.severe("A plugin is asking permission '" + permission + "' for a null player... Which plugin does that? Bastards!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,3 +78,4 @@ v 1.6:
|
||||||
- Stop throwing errors on an empty users file.
|
- Stop throwing errors on an empty users file.
|
||||||
- Optimize sorting to speedup permission tests.
|
- Optimize sorting to speedup permission tests.
|
||||||
- Fix superperms to pass all tests http://dev.bukkit.org/server-mods/superpermstest/
|
- Fix superperms to pass all tests http://dev.bukkit.org/server-mods/superpermstest/
|
||||||
|
- Optimizations include changing the return of comparePermissionString.
|
|
@ -10,6 +10,9 @@ import java.util.ArrayList;
|
||||||
import org.anjocaido.groupmanager.GroupManager;
|
import org.anjocaido.groupmanager.GroupManager;
|
||||||
import org.anjocaido.groupmanager.dataholder.WorldDataHolder;
|
import org.anjocaido.groupmanager.dataholder.WorldDataHolder;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -23,11 +26,11 @@ public class User extends DataUnit implements Cloneable {
|
||||||
private String group = null;
|
private String group = null;
|
||||||
private ArrayList<String> subGroups = new ArrayList<String>();
|
private ArrayList<String> subGroups = new ArrayList<String>();
|
||||||
/**
|
/**
|
||||||
*This one holds the fields in INFO node.
|
* This one holds the fields in INFO node. like prefix = 'c' or build =
|
||||||
* like prefix = 'c'
|
* false
|
||||||
* or build = false
|
|
||||||
*/
|
*/
|
||||||
private UserVariables variables = new UserVariables(this);
|
private UserVariables variables = new UserVariables(this);
|
||||||
|
private transient Player bukkitPlayer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -49,13 +52,14 @@ public class User extends DataUnit implements Cloneable {
|
||||||
for (String perm : this.getPermissionList()) {
|
for (String perm : this.getPermissionList()) {
|
||||||
clone.addPermission(perm);
|
clone.addPermission(perm);
|
||||||
}
|
}
|
||||||
//clone.variables = this.variables.clone();
|
// clone.variables = this.variables.clone();
|
||||||
//clone.flagAsChanged();
|
// clone.flagAsChanged();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this to deliver a user from one WorldDataHolder to another
|
* Use this to deliver a user from one WorldDataHolder to another
|
||||||
|
*
|
||||||
* @param dataSource
|
* @param dataSource
|
||||||
* @return null if given dataSource already contains the same user
|
* @return null if given dataSource already contains the same user
|
||||||
*/
|
*/
|
||||||
|
@ -72,7 +76,7 @@ public class User extends DataUnit implements Cloneable {
|
||||||
for (String perm : this.getPermissionList()) {
|
for (String perm : this.getPermissionList()) {
|
||||||
clone.addPermission(perm);
|
clone.addPermission(perm);
|
||||||
}
|
}
|
||||||
//clone.variables = this.variables.clone();
|
// clone.variables = this.variables.clone();
|
||||||
clone.flagAsChanged();
|
clone.flagAsChanged();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
@ -98,19 +102,21 @@ public class User extends DataUnit implements Cloneable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param group the group to set
|
* @param group
|
||||||
|
* the group to set
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void setGroup(String group) {
|
public void setGroup(String group) {
|
||||||
this.group = group;
|
this.group = group;
|
||||||
flagAsChanged();
|
flagAsChanged();
|
||||||
if (GroupManager.isLoaded())
|
if (GroupManager.isLoaded())
|
||||||
if(GroupManager.BukkitPermissions.player_join = false)
|
if (GroupManager.BukkitPermissions.player_join = false)
|
||||||
GroupManager.BukkitPermissions.updateAllPlayers();
|
GroupManager.BukkitPermissions.updateAllPlayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param group the group to set
|
* @param group
|
||||||
|
* the group to set
|
||||||
*/
|
*/
|
||||||
public void setGroup(Group group) {
|
public void setGroup(Group group) {
|
||||||
if (!this.getDataSource().groupExists(group.getName())) {
|
if (!this.getDataSource().groupExists(group.getName())) {
|
||||||
|
@ -127,10 +133,12 @@ public class User extends DataUnit implements Cloneable {
|
||||||
// Do we notify of the group change?
|
// Do we notify of the group change?
|
||||||
String defaultGroupName = getDataSource().getDefaultGroup().getName();
|
String defaultGroupName = getDataSource().getDefaultGroup().getName();
|
||||||
// if we were not in the default group
|
// if we were not in the default group
|
||||||
// or we were in the default group and the move is to a different group.
|
// or we were in the default group and the move is to a different
|
||||||
boolean notify = (!oldGroup.equalsIgnoreCase(defaultGroupName)) || ((oldGroup.equalsIgnoreCase(defaultGroupName)) && (!this.group.equalsIgnoreCase(defaultGroupName))) ;
|
// group.
|
||||||
|
boolean notify = (!oldGroup.equalsIgnoreCase(defaultGroupName)) || ((oldGroup.equalsIgnoreCase(defaultGroupName)) && (!this.group.equalsIgnoreCase(defaultGroupName)));
|
||||||
|
|
||||||
if (notify) GroupManager.notify(this.getName(), String.format(" moved to the group %s.", group.getName()));
|
if (notify)
|
||||||
|
GroupManager.notify(this.getName(), String.format(" moved to the group %s.", group.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,4 +223,19 @@ public class User extends DataUnit implements Cloneable {
|
||||||
if (GroupManager.BukkitPermissions.player_join = false)
|
if (GroupManager.BukkitPermissions.player_join = false)
|
||||||
GroupManager.BukkitPermissions.updateAllPlayers();
|
GroupManager.BukkitPermissions.updateAllPlayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public User updatePlayer(Player player) {
|
||||||
|
if (player != null) {
|
||||||
|
bukkitPlayer = player;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getBukkitPlayer() {
|
||||||
|
if (bukkitPlayer == null) {
|
||||||
|
bukkitPlayer = Bukkit.getPlayer(this.getName());
|
||||||
|
}
|
||||||
|
return bukkitPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,12 +44,13 @@ public class OverloadedWorldHolder extends WorldDataHolder {
|
||||||
@Override
|
@Override
|
||||||
public User getUser(String userName) {
|
public User getUser(String userName) {
|
||||||
//OVERLOADED CODE
|
//OVERLOADED CODE
|
||||||
if (overloadedUsers.containsKey(userName.toLowerCase())) {
|
String userNameLowered = userName.toLowerCase();
|
||||||
return overloadedUsers.get(userName.toLowerCase());
|
if (overloadedUsers.containsKey(userNameLowered)) {
|
||||||
|
return overloadedUsers.get(userNameLowered);
|
||||||
}
|
}
|
||||||
//END CODE
|
//END CODE
|
||||||
if (users.containsKey(userName.toLowerCase())) {
|
if (users.containsKey(userNameLowered)) {
|
||||||
return users.get(userName.toLowerCase());
|
return users.get(userNameLowered);
|
||||||
}
|
}
|
||||||
User newUser = createUser(userName);
|
User newUser = createUser(userName);
|
||||||
haveUsersChanged = true;
|
haveUsersChanged = true;
|
||||||
|
|
|
@ -268,9 +268,10 @@ public class WorldsHolder {
|
||||||
* @return OverloadedWorldHolder
|
* @return OverloadedWorldHolder
|
||||||
*/
|
*/
|
||||||
public OverloadedWorldHolder getWorldData(String worldName) {
|
public OverloadedWorldHolder getWorldData(String worldName) {
|
||||||
OverloadedWorldHolder data = worldsData.get(worldName.toLowerCase());
|
String worldNameLowered = worldName.toLowerCase();
|
||||||
if (mirrors.containsKey(worldName.toLowerCase())) {
|
OverloadedWorldHolder data = worldsData.get(worldNameLowered);
|
||||||
String realOne = mirrors.get(worldName.toLowerCase());
|
if (mirrors.containsKey(worldNameLowered)) {
|
||||||
|
String realOne = mirrors.get(worldNameLowered);
|
||||||
data = worldsData.get(realOne.toLowerCase());
|
data = worldsData.get(realOne.toLowerCase());
|
||||||
}
|
}
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.anjocaido.groupmanager.utils.PermissionCheckResult;
|
||||||
import org.anjocaido.groupmanager.utils.PermissionCheckResult.Type;
|
import org.anjocaido.groupmanager.utils.PermissionCheckResult.Type;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.permissions.Permission;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Everything here maintains the model created by Nijikokun
|
* Everything here maintains the model created by Nijikokun
|
||||||
|
@ -62,7 +63,7 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean permission(Player player, String permission) {
|
public boolean permission(Player player, String permission) {
|
||||||
return checkUserPermission(ph.getUser(player.getName()), permission);
|
return checkUserPermission(ph.getUser(player.getName()).updatePlayer(player), permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,7 +150,8 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Collections.sort(playerPermArray, StringPermissionComparator.getInstance());
|
// Collections.sort(playerPermArray,
|
||||||
|
// StringPermissionComparator.getInstance());
|
||||||
|
|
||||||
return playerPermArray;
|
return playerPermArray;
|
||||||
}
|
}
|
||||||
|
@ -243,7 +245,8 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
/**
|
/**
|
||||||
* Check if user can build. Checks inheritance and subgroups.
|
* Check if user can build. Checks inheritance and subgroups.
|
||||||
*
|
*
|
||||||
* @param userName Player's name
|
* @param userName
|
||||||
|
* Player's name
|
||||||
* @return true if the user can build
|
* @return true if the user can build
|
||||||
*/
|
*/
|
||||||
public boolean canUserBuild(String userName) {
|
public boolean canUserBuild(String userName) {
|
||||||
|
@ -283,8 +286,8 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the specified group for the Info Build node.
|
* Checks the specified group for the Info Build node. Does NOT check
|
||||||
* Does NOT check inheritance
|
* inheritance
|
||||||
*
|
*
|
||||||
* @param groupName
|
* @param groupName
|
||||||
* @return true if can build
|
* @return true if can build
|
||||||
|
@ -615,15 +618,8 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
result.askedPermission = permission;
|
result.askedPermission = permission;
|
||||||
result.owner = user;
|
result.owner = user;
|
||||||
for (String access : user.getPermissionList()) {
|
for (String access : user.getPermissionList()) {
|
||||||
if (comparePermissionString(access, permission)) {
|
result.resultType = comparePermissionString(access, permission);
|
||||||
result.accessLevel = access;
|
if (result.resultType != PermissionCheckResult.Type.NOTFOUND) {
|
||||||
if (access.charAt(0) == '-') {
|
|
||||||
result.resultType = PermissionCheckResult.Type.NEGATION;
|
|
||||||
} else if (access.charAt(0) == '+') {
|
|
||||||
result.resultType = PermissionCheckResult.Type.EXCEPTION;
|
|
||||||
} else {
|
|
||||||
result.resultType = PermissionCheckResult.Type.FOUND;
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -645,15 +641,8 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
result.owner = group;
|
result.owner = group;
|
||||||
result.askedPermission = permission;
|
result.askedPermission = permission;
|
||||||
for (String access : group.getPermissionList()) {
|
for (String access : group.getPermissionList()) {
|
||||||
if (comparePermissionString(access, permission)) {
|
result.resultType = comparePermissionString(access, permission);
|
||||||
result.accessLevel = access;
|
if (result.resultType != PermissionCheckResult.Type.NOTFOUND) {
|
||||||
if (access.charAt(0) == '-') {
|
|
||||||
result.resultType = PermissionCheckResult.Type.NEGATION;
|
|
||||||
} else if (access.charAt(0) == '+') {
|
|
||||||
result.resultType = PermissionCheckResult.Type.EXCEPTION;
|
|
||||||
} else {
|
|
||||||
result.resultType = PermissionCheckResult.Type.FOUND;
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -670,7 +659,7 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
*/
|
*/
|
||||||
public boolean checkUserPermission(User user, String permission) {
|
public boolean checkUserPermission(User user, String permission) {
|
||||||
PermissionCheckResult result = checkFullGMPermission(user, permission, true);
|
PermissionCheckResult result = checkFullGMPermission(user, permission, true);
|
||||||
if (result.resultType.equals(PermissionCheckResult.Type.EXCEPTION) || result.resultType.equals(PermissionCheckResult.Type.FOUND)) {
|
if (result.resultType == PermissionCheckResult.Type.EXCEPTION || result.resultType == PermissionCheckResult.Type.FOUND) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,8 +680,8 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check user and groups with inheritance and Bukkit if bukkit = true
|
* Check user and groups with inheritance and Bukkit if bukkit = true return
|
||||||
* return a PermissionCheckResult.
|
* a PermissionCheckResult.
|
||||||
*
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param targetPermission
|
* @param targetPermission
|
||||||
|
@ -708,31 +697,33 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkBukkit == true) {
|
if (checkBukkit) {
|
||||||
// Check Bukkit perms to support plugins which add perms via code (Heroes).
|
// Check Bukkit perms to support plugins which add perms via code
|
||||||
final Player player = Bukkit.getPlayer(user.getName());
|
// (Heroes).
|
||||||
if ((player != null) && (player.hasPermission(targetPermission))) {
|
final Player player = user.getBukkitPlayer();
|
||||||
result.resultType = PermissionCheckResult.Type.FOUND;
|
final Permission bukkitPerm = Bukkit.getPluginManager().getPermission(targetPermission);
|
||||||
|
if (player != null && bukkitPerm != null) {
|
||||||
|
result.resultType = player.hasPermission(bukkitPerm) ? PermissionCheckResult.Type.FOUND : PermissionCheckResult.Type.NEGATION;
|
||||||
result.owner = user;
|
result.owner = user;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PermissionCheckResult resultUser = checkUserOnlyPermission(user, targetPermission);
|
PermissionCheckResult resultUser = checkUserOnlyPermission(user, targetPermission);
|
||||||
if (!resultUser.resultType.equals(PermissionCheckResult.Type.NOTFOUND)) {
|
if (resultUser.resultType != PermissionCheckResult.Type.NOTFOUND) {
|
||||||
return resultUser;
|
return resultUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IT ONLY CHECKS GROUPS PERMISSIONS IF RESULT FOR USER IS NOT FOUND
|
// IT ONLY CHECKS GROUPS PERMISSIONS IF RESULT FOR USER IS NOT FOUND
|
||||||
PermissionCheckResult resultGroup = checkGroupPermissionWithInheritance(user.getGroup(), targetPermission);
|
PermissionCheckResult resultGroup = checkGroupPermissionWithInheritance(user.getGroup(), targetPermission);
|
||||||
if (!resultGroup.resultType.equals(PermissionCheckResult.Type.NOTFOUND)) {
|
if (resultGroup.resultType != PermissionCheckResult.Type.NOTFOUND) {
|
||||||
return resultGroup;
|
return resultGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SUBGROUPS CHECK
|
// SUBGROUPS CHECK
|
||||||
for (Group subGroup : user.subGroupListCopy()) {
|
for (Group subGroup : user.subGroupListCopy()) {
|
||||||
PermissionCheckResult resultSubGroup = checkGroupPermissionWithInheritance(subGroup, targetPermission);
|
PermissionCheckResult resultSubGroup = checkGroupPermissionWithInheritance(subGroup, targetPermission);
|
||||||
if (!resultSubGroup.resultType.equals(PermissionCheckResult.Type.NOTFOUND)) {
|
if (resultSubGroup.resultType != PermissionCheckResult.Type.NOTFOUND) {
|
||||||
return resultSubGroup;
|
return resultSubGroup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -994,21 +985,25 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
*
|
*
|
||||||
* @param userAccessLevel
|
* @param userAccessLevel
|
||||||
* @param fullPermissionName
|
* @param fullPermissionName
|
||||||
* @return true if found a matching token. false if not.
|
* @return PermissionCheckResult.Type
|
||||||
*/
|
*/
|
||||||
public boolean comparePermissionString(String userAccessLevel, String fullPermissionName) {
|
public PermissionCheckResult.Type comparePermissionString(String userAccessLevel, String fullPermissionName) {
|
||||||
int userAccessLevelLength;
|
int userAccessLevelLength;
|
||||||
if (userAccessLevel == null || fullPermissionName == null
|
if (userAccessLevel == null || fullPermissionName == null || fullPermissionName.length() == 0 || (userAccessLevelLength = userAccessLevel.length()) == 0) {
|
||||||
|| fullPermissionName.length() == 0 || (userAccessLevelLength = userAccessLevel.length()) == 0) {
|
return PermissionCheckResult.Type.NOTFOUND;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PermissionCheckResult.Type result = PermissionCheckResult.Type.FOUND;
|
||||||
int userAccessLevelOffset = 0;
|
int userAccessLevelOffset = 0;
|
||||||
if (userAccessLevel.charAt(0) == '+' || userAccessLevel.charAt(0) == '-') {
|
if (userAccessLevel.charAt(0) == '+') {
|
||||||
userAccessLevelOffset = 1;
|
userAccessLevelOffset = 1;
|
||||||
|
result = PermissionCheckResult.Type.EXCEPTION;
|
||||||
|
} else if (userAccessLevel.charAt(0) == '-') {
|
||||||
|
userAccessLevelOffset = 1;
|
||||||
|
result = PermissionCheckResult.Type.NEGATION;
|
||||||
}
|
}
|
||||||
if ("*".regionMatches(0, userAccessLevel, userAccessLevelOffset, userAccessLevelLength - userAccessLevelOffset)) {
|
if ("*".regionMatches(0, userAccessLevel, userAccessLevelOffset, userAccessLevelLength - userAccessLevelOffset)) {
|
||||||
return true;
|
return result;
|
||||||
}
|
}
|
||||||
int fullPermissionNameOffset;
|
int fullPermissionNameOffset;
|
||||||
if (fullPermissionName.charAt(0) == '+' || fullPermissionName.charAt(0) == '-') {
|
if (fullPermissionName.charAt(0) == '+' || fullPermissionName.charAt(0) == '-') {
|
||||||
|
@ -1018,14 +1013,15 @@ public class AnjoPermissionsHandler extends PermissionsReaderInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userAccessLevel.charAt(userAccessLevel.length() - 1) == '*') {
|
if (userAccessLevel.charAt(userAccessLevel.length() - 1) == '*') {
|
||||||
return userAccessLevel.regionMatches(true, userAccessLevelOffset, fullPermissionName, fullPermissionNameOffset, userAccessLevelLength - userAccessLevelOffset - 1);
|
return userAccessLevel.regionMatches(true, userAccessLevelOffset, fullPermissionName, fullPermissionNameOffset, userAccessLevelLength - userAccessLevelOffset - 1) ?
|
||||||
|
result : PermissionCheckResult.Type.NOTFOUND;
|
||||||
} else {
|
} else {
|
||||||
return userAccessLevel.regionMatches(true, userAccessLevelOffset, fullPermissionName, fullPermissionNameOffset,
|
return userAccessLevel.regionMatches(true, userAccessLevelOffset, fullPermissionName, fullPermissionNameOffset,
|
||||||
Math.max(userAccessLevelLength - userAccessLevelOffset, fullPermissionName.length() - fullPermissionNameOffset));
|
Math.max(userAccessLevelLength - userAccessLevelOffset, fullPermissionName.length() - fullPermissionNameOffset)) ?
|
||||||
|
result : PermissionCheckResult.Type.NOTFOUND;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of all groups.
|
* Returns a list of all groups.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue