mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-22 16:41:46 +03:00
[ANY] IDEA code inspection
This commit is contained in:
parent
1cbf95f815
commit
d7e9850f9a
21 changed files with 93 additions and 140 deletions
|
@ -50,17 +50,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
tasks.register('sourcesJar') {
|
||||
from sourceSets.main.allJava
|
||||
archiveClassifier.set('sources')
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
tasks.register('javadocJar') {
|
||||
from javadoc
|
||||
archiveClassifier.set('javadoc')
|
||||
}
|
||||
|
||||
task cleanjar(type: Jar, dependsOn: jar) {
|
||||
tasks.register('cleanjar') {
|
||||
archiveClassifier.set('clean')
|
||||
manifest.attributes("Main-Class": mainClassName,
|
||||
"Premain-Class": mainAgentName,
|
||||
|
@ -117,13 +117,13 @@ pack project(':LauncherAPI')
|
|||
compileOnlyA 'org.apache.logging.log4j:log4j-core:2.14.1'
|
||||
}
|
||||
|
||||
task hikari(type: Copy) {
|
||||
tasks.register('hikari') {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
into "$buildDir/libs/libraries/hikaricp"
|
||||
from configurations.hikari
|
||||
}
|
||||
|
||||
task launch4j(type: Copy) {
|
||||
tasks.register('launch4j') {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
into "$buildDir/libs/libraries/launch4j"
|
||||
from(configurations.launch4j.collect {
|
||||
|
@ -141,20 +141,20 @@ task launch4j(type: Copy) {
|
|||
}
|
||||
}
|
||||
|
||||
task dumpLibs(type: Copy) {
|
||||
tasks.register('dumpLibs') {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
dependsOn tasks.hikari, tasks.launch4j
|
||||
into "$buildDir/libs/libraries"
|
||||
from configurations.bundleOnly
|
||||
}
|
||||
|
||||
task dumpCompileOnlyLibs(type: Copy) {
|
||||
tasks.register('dumpCompileOnlyLibs') {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
into "$buildDir/libs/launcher-libraries-compile"
|
||||
from configurations.compileOnlyA
|
||||
}
|
||||
|
||||
task bundle(type: Zip) {
|
||||
tasks.register('bundle') {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.jar
|
||||
archiveFileName = 'LaunchServer.zip'
|
||||
|
@ -165,7 +165,7 @@ task bundle(type: Zip) {
|
|||
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
|
||||
}
|
||||
|
||||
task dumpClientLibs(type: Copy) {
|
||||
tasks.register('dumpClientLibs') {
|
||||
dependsOn parent.childProjects.Launcher.tasks.build
|
||||
into "$buildDir/libs/launcher-libraries"
|
||||
from parent.childProjects.Launcher.tasks.dumpLibs
|
||||
|
|
|
@ -29,12 +29,12 @@
|
|||
"Multi-Release": "true")
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
tasks.register('sourcesJar') {
|
||||
from sourceSets.main.allJava
|
||||
archiveClassifier.set('sources')
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
tasks.register('javadocJar') {
|
||||
from javadoc
|
||||
archiveClassifier.set('javadoc')
|
||||
}
|
||||
|
@ -53,14 +53,14 @@ pack project(':LauncherAPI')
|
|||
pack group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty']
|
||||
}
|
||||
|
||||
task genRuntimeJS(type: Zip) {
|
||||
tasks.register('genRuntimeJS') {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
archiveFileName = "runtime.zip"
|
||||
destinationDirectory = file("${buildDir}/tmp")
|
||||
from "runtime/"
|
||||
}
|
||||
|
||||
task dumpLibs(type: Copy) {
|
||||
tasks.register('dumpLibs') {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
into "$buildDir/libs/libraries"
|
||||
from configurations.bundle
|
||||
|
|
|
@ -13,8 +13,7 @@ public class BasicLauncherEventHandler implements RequestService.EventHandler {
|
|||
|
||||
@Override
|
||||
public <T extends WebSocketEvent> boolean eventHandle(T event) {
|
||||
if (event instanceof SecurityReportRequestEvent) {
|
||||
SecurityReportRequestEvent event1 = (SecurityReportRequestEvent) event;
|
||||
if (event instanceof SecurityReportRequestEvent event1) {
|
||||
if (event1.action == SecurityReportRequestEvent.ReportAction.CRASH) {
|
||||
LauncherEngine.exitLauncher(80);
|
||||
} else if (event1.action == SecurityReportRequestEvent.ReportAction.TOKEN_EXPIRED) {
|
||||
|
@ -24,14 +23,12 @@ public <T extends WebSocketEvent> boolean eventHandle(T event) {
|
|||
LogHelper.error(e);
|
||||
}
|
||||
}
|
||||
} else if (event instanceof ExtendedTokenRequestEvent) {
|
||||
ExtendedTokenRequestEvent event1 = (ExtendedTokenRequestEvent) event;
|
||||
} else if (event instanceof ExtendedTokenRequestEvent event1) {
|
||||
String token = event1.getExtendedToken();
|
||||
if (token != null) {
|
||||
Request.addExtendedToken(event1.getExtendedTokenName(), token);
|
||||
}
|
||||
} else if (event instanceof NotificationEvent) {
|
||||
NotificationEvent n = (NotificationEvent) event;
|
||||
} else if (event instanceof NotificationEvent n) {
|
||||
if (DialogService.isNotificationsAvailable()) {
|
||||
DialogService.createNotification(n.icon, n.head, n.message);
|
||||
}
|
||||
|
|
|
@ -154,13 +154,11 @@ public static void verifyNoAgent() {
|
|||
}
|
||||
|
||||
public static LauncherGuard tryGetStdGuard() {
|
||||
switch (Launcher.getConfig().guardType) {
|
||||
case "no":
|
||||
return new LauncherNoGuard();
|
||||
case "wrapper":
|
||||
return new LauncherWrapperGuard();
|
||||
}
|
||||
return null;
|
||||
return switch (Launcher.getConfig().guardType) {
|
||||
case "no" -> new LauncherNoGuard();
|
||||
case "wrapper" -> new LauncherWrapperGuard();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static RequestService initOffline() {
|
||||
|
|
|
@ -79,20 +79,14 @@ private static CheckClassResultApi fromCheckClassResult(LauncherTrustManager.Che
|
|||
|
||||
private static CheckClassResultTypeApi fromType(LauncherTrustManager.CheckClassResultType type) {
|
||||
if (type == null) return null;
|
||||
switch (type) {
|
||||
case NOT_SIGNED:
|
||||
return CheckClassResultTypeApi.NOT_SIGNED;
|
||||
case SUCCESS:
|
||||
return CheckClassResultTypeApi.SUCCESS;
|
||||
case UNTRUSTED:
|
||||
return CheckClassResultTypeApi.UNTRUSTED;
|
||||
case UNVERIFED:
|
||||
return CheckClassResultTypeApi.UNVERIFED;
|
||||
case UNCOMPAT:
|
||||
return CheckClassResultTypeApi.UNCOMPAT;
|
||||
default:
|
||||
return CheckClassResultTypeApi.UNKNOWN;
|
||||
}
|
||||
return switch (type) {
|
||||
case NOT_SIGNED -> CheckClassResultTypeApi.NOT_SIGNED;
|
||||
case SUCCESS -> CheckClassResultTypeApi.SUCCESS;
|
||||
case UNTRUSTED -> CheckClassResultTypeApi.UNTRUSTED;
|
||||
case UNVERIFED -> CheckClassResultTypeApi.UNVERIFED;
|
||||
case UNCOMPAT -> CheckClassResultTypeApi.UNCOMPAT;
|
||||
default -> CheckClassResultTypeApi.UNKNOWN;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ public static void checkCertificatesSuccess(X509Certificate[] certs) throws Exce
|
|||
}
|
||||
|
||||
public static String findLibrary(ClassLoader classLoader, String library) {
|
||||
if (classLoader instanceof ClientClassLoader) {
|
||||
ClientClassLoader clientClassLoader = (ClientClassLoader) classLoader;
|
||||
if (classLoader instanceof ClientClassLoader clientClassLoader) {
|
||||
return clientClassLoader.findLibrary(library);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -21,12 +21,12 @@ api project(':LauncherCore')
|
|||
archiveClassifier.set('clean')
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
tasks.register('sourcesJar') {
|
||||
from sourceSets.main.allJava
|
||||
archiveClassifier.set('sources')
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
tasks.register('javadocJar') {
|
||||
from javadoc
|
||||
archiveClassifier.set('javadoc')
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ public LauncherConfig(String address, ECPublicKey ecdsaPublicKey, RSAPublicKey r
|
|||
this.address = address;
|
||||
this.ecdsaPublicKey = ecdsaPublicKey;
|
||||
this.rsaPublicKey = rsaPublicKey;
|
||||
this.runtime = Collections.unmodifiableMap(new HashMap<>(runtime));
|
||||
this.runtime = Map.copyOf(runtime);
|
||||
this.projectName = projectName;
|
||||
this.clientPort = 32148;
|
||||
guardType = "no";
|
||||
|
@ -102,7 +102,7 @@ public LauncherConfig(String address, ECPublicKey ecdsaPublicKey, RSAPublicKey r
|
|||
|
||||
public LauncherConfig(String address, Map<String, byte[]> runtime, String projectName, LauncherEnvironment env, LauncherTrustManager trustManager) {
|
||||
this.address = address;
|
||||
this.runtime = Collections.unmodifiableMap(new HashMap<>(runtime));
|
||||
this.runtime = Map.copyOf(runtime);
|
||||
this.projectName = projectName;
|
||||
this.clientPort = 32148;
|
||||
this.trustManager = trustManager;
|
||||
|
|
|
@ -76,8 +76,7 @@ public <T extends WebSocketEvent> void processEventHandlers(T event) {
|
|||
|
||||
@SuppressWarnings({"unchecked", "deprecation"})
|
||||
public <T extends WebSocketEvent> void eventHandle(T webSocketEvent) {
|
||||
if (webSocketEvent instanceof RequestEvent) {
|
||||
RequestEvent event = (RequestEvent) webSocketEvent;
|
||||
if (webSocketEvent instanceof RequestEvent event) {
|
||||
if (event.requestUUID == null) {
|
||||
LogHelper.warning("Request event type %s.requestUUID is null", event.getType() == null ? "null" : event.getType());
|
||||
return;
|
||||
|
|
|
@ -51,15 +51,13 @@ protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Except
|
|||
return;
|
||||
}
|
||||
|
||||
if (msg instanceof FullHttpResponse) {
|
||||
final FullHttpResponse response = (FullHttpResponse) msg;
|
||||
if (msg instanceof final FullHttpResponse response) {
|
||||
throw new Exception("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content="
|
||||
+ response.content().toString(CharsetUtil.UTF_8) + ')');
|
||||
}
|
||||
|
||||
final WebSocketFrame frame = (WebSocketFrame) msg;
|
||||
if (frame instanceof TextWebSocketFrame) {
|
||||
final TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
|
||||
if (frame instanceof final TextWebSocketFrame textFrame) {
|
||||
if (LogHelper.isDevEnabled()) {
|
||||
LogHelper.dev("Message: %s", textFrame.text());
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@
|
|||
archiveClassifier.set('clean')
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
tasks.register('sourcesJar') {
|
||||
from sourceSets.main.allJava
|
||||
archiveClassifier.set('sources')
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
tasks.register('javadocJar') {
|
||||
from javadoc
|
||||
archiveClassifier.set('javadoc')
|
||||
}
|
||||
|
|
|
@ -30,16 +30,11 @@ public HashedDir(HInput input) throws IOException {
|
|||
// Read entry
|
||||
HashedEntry entry;
|
||||
Type type = Type.read(input);
|
||||
switch (type) {
|
||||
case FILE:
|
||||
entry = new HashedFile(input);
|
||||
break;
|
||||
case DIR:
|
||||
entry = new HashedDir(input);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported hashed entry type: " + type.name());
|
||||
}
|
||||
entry = switch (type) {
|
||||
case FILE -> new HashedFile(input);
|
||||
case DIR -> new HashedDir(input);
|
||||
default -> throw new AssertionError("Unsupported hashed entry type: " + type.name());
|
||||
};
|
||||
|
||||
// Try add entry to map
|
||||
VerifyHelper.putIfAbsent(map, name, entry, String.format("Duplicate dir entry: '%s'", name));
|
||||
|
@ -157,13 +152,13 @@ private HashedDir sideDiff(HashedDir other, FileNameMatcher matcher, Deque<Strin
|
|||
|
||||
// Compare entries based on type
|
||||
switch (type) {
|
||||
case FILE:
|
||||
case FILE -> {
|
||||
HashedFile file = (HashedFile) entry;
|
||||
HashedFile otherFile = (HashedFile) otherEntry;
|
||||
if (mismatchList && shouldUpdate && !file.isSame(otherFile))
|
||||
diff.map.put(name, entry);
|
||||
break;
|
||||
case DIR:
|
||||
}
|
||||
case DIR -> {
|
||||
HashedDir dir = (HashedDir) entry;
|
||||
HashedDir otherDir = (HashedDir) otherEntry;
|
||||
if (mismatchList || shouldUpdate) { // Maybe isn't need to go deeper?
|
||||
|
@ -171,9 +166,8 @@ private HashedDir sideDiff(HashedDir other, FileNameMatcher matcher, Deque<Strin
|
|||
if (!mismatch.isEmpty())
|
||||
diff.map.put(name, mismatch);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported hashed entry type: " + type.name());
|
||||
}
|
||||
default -> throw new AssertionError("Unsupported hashed entry type: " + type.name());
|
||||
}
|
||||
|
||||
// Remove this path entry
|
||||
|
@ -209,13 +203,13 @@ public HashedDir sideCompare(HashedDir other, FileNameMatcher matcher, Deque<Str
|
|||
|
||||
// Compare entries based on type
|
||||
switch (type) {
|
||||
case FILE:
|
||||
case FILE -> {
|
||||
HashedFile file = (HashedFile) entry;
|
||||
HashedFile otherFile = (HashedFile) otherEntry;
|
||||
if (mismatchList && shouldUpdate && file.isSame(otherFile))
|
||||
diff.map.put(name, entry);
|
||||
break;
|
||||
case DIR:
|
||||
}
|
||||
case DIR -> {
|
||||
HashedDir dir = (HashedDir) entry;
|
||||
HashedDir otherDir = (HashedDir) otherEntry;
|
||||
if (mismatchList || shouldUpdate) { // Maybe isn't need to go deeper?
|
||||
|
@ -223,9 +217,8 @@ public HashedDir sideCompare(HashedDir other, FileNameMatcher matcher, Deque<Str
|
|||
if (!mismatch.isEmpty())
|
||||
diff.map.put(name, mismatch);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported hashed entry type: " + type.name());
|
||||
}
|
||||
default -> throw new AssertionError("Unsupported hashed entry type: " + type.name());
|
||||
}
|
||||
|
||||
// Remove this path entry
|
||||
|
|
|
@ -42,14 +42,11 @@ public BigInteger readBigInteger(int maxBytes) throws IOException {
|
|||
|
||||
public boolean readBoolean() throws IOException {
|
||||
int b = readUnsignedByte();
|
||||
switch (b) {
|
||||
case 0b0:
|
||||
return false;
|
||||
case 0b1:
|
||||
return true;
|
||||
default:
|
||||
throw new IOException("Invalid boolean state: " + b);
|
||||
}
|
||||
return switch (b) {
|
||||
case 0b0 -> false;
|
||||
case 0b1 -> true;
|
||||
default -> throw new IOException("Invalid boolean state: " + b);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -84,20 +84,19 @@ public static String[] parseCommand(CharSequence line) throws CommandException {
|
|||
|
||||
// Append next char
|
||||
switch (ch) {
|
||||
case '"': // "abc"de, "abc""de" also allowed
|
||||
case '"' -> { // "abc"de, "abc""de" also allowed
|
||||
quoted = !quoted;
|
||||
wasQuoted = true;
|
||||
break;
|
||||
case '\\': // All escapes, including spaces etc
|
||||
}
|
||||
case '\\' -> { // All escapes, including spaces etc
|
||||
if (i + 1 >= line.length())
|
||||
throw new CommandException("Escape character is not specified");
|
||||
char next = line.charAt(i + 1);
|
||||
builder.append(next);
|
||||
i++;
|
||||
break;
|
||||
default: // Default char, simply append
|
||||
builder.append(ch);
|
||||
break;
|
||||
}
|
||||
default -> // Default char, simply append
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,17 +14,12 @@ public class FormatHelper {
|
|||
public static Ansi rawAnsiFormat(LogHelper.Level level, String dateTime, boolean sub) {
|
||||
Ansi.Color levelColor;
|
||||
boolean bright = level != LogHelper.Level.DEBUG;
|
||||
switch (level) {
|
||||
case WARNING:
|
||||
levelColor = Ansi.Color.YELLOW;
|
||||
break;
|
||||
case ERROR:
|
||||
levelColor = Ansi.Color.RED;
|
||||
break;
|
||||
default: // INFO, DEBUG, Unknown
|
||||
levelColor = Ansi.Color.WHITE;
|
||||
break;
|
||||
}
|
||||
levelColor = switch (level) {
|
||||
case WARNING -> Ansi.Color.YELLOW;
|
||||
case ERROR -> Ansi.Color.RED;
|
||||
default -> // INFO, DEBUG, Unknown
|
||||
Ansi.Color.WHITE;
|
||||
};
|
||||
|
||||
// Date-time
|
||||
Ansi ansi = new Ansi();
|
||||
|
|
|
@ -139,12 +139,11 @@ public static String getIP(SocketAddress address) {
|
|||
|
||||
public static Path getRoot() {
|
||||
switch (JVMHelper.OS_TYPE) {
|
||||
case MUSTDIE: {
|
||||
case MUSTDIE -> {
|
||||
String drive = System.getenv("SystemDrive").concat("\\");
|
||||
return Paths.get(drive);
|
||||
}
|
||||
case LINUX:
|
||||
case MACOSX: {
|
||||
case LINUX, MACOSX -> {
|
||||
return Paths.get("/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,21 +33,11 @@ public Slf4jLogHelperImpl() {
|
|||
@Override
|
||||
public void log(LogHelper.Level level, String message, boolean sub) {
|
||||
switch (level) {
|
||||
case DEV:
|
||||
logger.trace(message);
|
||||
break;
|
||||
case DEBUG:
|
||||
logger.debug(message);
|
||||
break;
|
||||
case INFO:
|
||||
logger.info(message);
|
||||
break;
|
||||
case WARNING:
|
||||
logger.warn(message);
|
||||
break;
|
||||
case ERROR:
|
||||
logger.error(message);
|
||||
break;
|
||||
case DEV -> logger.trace(message);
|
||||
case DEBUG -> logger.debug(message);
|
||||
case INFO -> logger.info(message);
|
||||
case WARNING -> logger.warn(message);
|
||||
case ERROR -> logger.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,12 +30,12 @@
|
|||
"Multi-Release": "true")
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
tasks.register('sourcesJar') {
|
||||
from sourceSets.main.allJava
|
||||
archiveClassifier.set('sources')
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
tasks.register('javadocJar') {
|
||||
from javadoc
|
||||
archiveClassifier.set('javadoc')
|
||||
}
|
||||
|
|
|
@ -167,18 +167,11 @@ public void run(String... args) throws Throwable {
|
|||
real_args = new String[args.length - 1];
|
||||
System.arraycopy(args, 1, real_args, 0, args.length - 1);
|
||||
} else real_args = args;
|
||||
Launch launch;
|
||||
switch (config.classLoaderConfig) {
|
||||
case LAUNCHER:
|
||||
launch = new ClasspathLaunch();
|
||||
break;
|
||||
case MODULE:
|
||||
launch = new ModuleLaunch();
|
||||
break;
|
||||
default:
|
||||
launch = new SimpleLaunch();
|
||||
break;
|
||||
}
|
||||
Launch launch = switch (config.classLoaderConfig) {
|
||||
case LAUNCHER -> new ClasspathLaunch();
|
||||
case MODULE -> new ModuleLaunch();
|
||||
default -> new SimpleLaunch();
|
||||
};
|
||||
LogHelper.info("Start Minecraft Server");
|
||||
LogHelper.debug("Invoke main method %s with %s", classname, launch.getClass().getName());
|
||||
try {
|
||||
|
|
|
@ -41,10 +41,10 @@ public void run(String... args) throws Exception {
|
|||
}
|
||||
context.workdir = IOHelper.WORKING_DIR;
|
||||
LogHelper.info("Search .jar files in %s", context.workdir.toAbsolutePath());
|
||||
IOHelper.walk(context.workdir, new SimpleFileVisitor<Path>() {
|
||||
IOHelper.walk(context.workdir, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if(file.getFileName().toString().endsWith(".jar")) {
|
||||
if (file.getFileName().toString().endsWith(".jar")) {
|
||||
context.files.add(file);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
|
|
10
build.gradle
10
build.gradle
|
@ -43,10 +43,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
|
||||
options.incremental = true // one flag, and things will get MUCH faster
|
||||
tasks.withType(JavaCompile).tap {
|
||||
configureEach {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
|
||||
options.incremental = true // one flag, and things will get MUCH faster
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue