[FEATURE] Cкачивание zip архивами - часть 2 обработка со стороны лаунчсервера.

This commit is contained in:
Zaxar163 2019-07-19 18:00:19 +03:00
parent 8ebe6983f3
commit 4ab67460e2
6 changed files with 106 additions and 12 deletions

View file

@ -33,6 +33,7 @@
import java.util.Timer; import java.util.Timer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.ZipOutputStream;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
@ -43,6 +44,7 @@
import pro.gravit.launcher.LauncherConfig; import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launcher.NeedGarbageCollection; import pro.gravit.launcher.NeedGarbageCollection;
import pro.gravit.launcher.hasher.HashedDir; import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.hasher.HashedEntry;
import pro.gravit.launcher.hwid.HWIDProvider; import pro.gravit.launcher.hwid.HWIDProvider;
import pro.gravit.launcher.managers.ConfigManager; import pro.gravit.launcher.managers.ConfigManager;
import pro.gravit.launcher.managers.GarbageManager; import pro.gravit.launcher.managers.GarbageManager;
@ -67,6 +69,7 @@
import pro.gravit.launchserver.components.RegLimiterComponent; import pro.gravit.launchserver.components.RegLimiterComponent;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig; import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.dao.provider.DaoProvider; import pro.gravit.launchserver.dao.provider.DaoProvider;
import pro.gravit.launchserver.hasher.UpdateData;
import pro.gravit.launchserver.manangers.*; import pro.gravit.launchserver.manangers.*;
import pro.gravit.launchserver.manangers.hook.AuthHookManager; import pro.gravit.launchserver.manangers.hook.AuthHookManager;
import pro.gravit.launchserver.manangers.hook.BuildHookManager; import pro.gravit.launchserver.manangers.hook.BuildHookManager;
@ -282,6 +285,7 @@ public class NettyConfig {
public boolean fileServerEnabled; public boolean fileServerEnabled;
public boolean sendExceptionEnabled; public boolean sendExceptionEnabled;
public boolean ipForwarding; public boolean ipForwarding;
public boolean directoryListing = true;
public String launcherURL; public String launcherURL;
public String downloadURL; public String downloadURL;
public String launcherEXEURL; public String launcherEXEURL;
@ -463,6 +467,8 @@ public static void main(String... args) throws Throwable {
public static Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null; public static Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
public final Path optimizedUpdatesDir;
public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException, InvalidKeySpecException { public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException, InvalidKeySpecException {
this.dir = dir; this.dir = dir;
this.testEnv = testEnv; this.testEnv = testEnv;
@ -489,6 +495,7 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
publicKeyFile = dir.resolve("public.key"); publicKeyFile = dir.resolve("public.key");
privateKeyFile = dir.resolve("private.key"); privateKeyFile = dir.resolve("private.key");
updatesDir = dir.resolve("updates"); updatesDir = dir.resolve("updates");
optimizedUpdatesDir = dir.resolve("optimized_updates");
profilesDir = dir.resolve("profiles"); profilesDir = dir.resolve("profiles");
caCertFile = dir.resolve("ca.crt"); caCertFile = dir.resolve("ca.crt");
@ -681,6 +688,9 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
// Sync updates dir // Sync updates dir
if (!IOHelper.isDir(updatesDir)) if (!IOHelper.isDir(updatesDir))
Files.createDirectory(updatesDir); Files.createDirectory(updatesDir);
if (!IOHelper.isDir(optimizedUpdatesDir))
Files.createDirectory(optimizedUpdatesDir);
updatesDirMap = null; // small hack
syncUpdatesDir(null); syncUpdatesDir(null);
// Sync profiles dir // Sync profiles dir
@ -818,6 +828,7 @@ private void generateConfigIfNotExists(boolean testEnv) throws IOException {
newConfig.stripLineNumbers = true; newConfig.stripLineNumbers = true;
newConfig.deleteTempFiles = true; newConfig.deleteTempFiles = true;
newConfig.isWarningMissArchJava = true; newConfig.isWarningMissArchJava = true;
newConfig.zipDownload = true;
newConfig.components = new HashMap<>(); newConfig.components = new HashMap<>();
AuthLimiterComponent authLimiterComponent = new AuthLimiterComponent(); AuthLimiterComponent authLimiterComponent = new AuthLimiterComponent();
@ -931,6 +942,7 @@ public void syncProfilesDir() throws IOException {
public void syncUpdatesDir(Collection<String> dirs) throws IOException { public void syncUpdatesDir(Collection<String> dirs) throws IOException {
LogHelper.info("Syncing updates dir"); LogHelper.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16); Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
boolean work = updatesDirMap != null;
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(updatesDir)) { try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(updatesDir)) {
for (final Path updateDir : dirStream) { for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir)) if (Files.isHidden(updateDir))
@ -956,12 +968,39 @@ public void syncUpdatesDir(Collection<String> dirs) throws IOException {
// Sync and sign update dir // Sync and sign update dir
LogHelper.info("Syncing '%s' update dir", name); LogHelper.info("Syncing '%s' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true); HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
if (work && config.zipDownload) processUpdate(updateDir, updateHDir, name);
newUpdatesDirMap.put(name, updateHDir); newUpdatesDirMap.put(name, updateHDir);
} }
} }
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap); updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
} }
private void processUpdate(Path updateDir, HashedDir updateHDir, String name) throws IOException {
updateHDir.walk(IOHelper.CROSS_SEPARATOR, (path, filename, entry) -> {
if (entry.getType().equals(HashedEntry.Type.DIR)) {
if (UpdateData.needsZip((HashedDir) entry)) {
Path p = updateDir.resolve(path);
Path out = optimizedUpdatesDir.resolve(name).resolve(path + ".zip");
try (ZipOutputStream compressed = new ZipOutputStream(IOHelper.newOutput(out))) {
IOHelper.walk(p, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
compressed.putNextEntry(IOHelper.newZipEntry(
p.relativize(file).toString()
.replace(IOHelper.PLATFORM_SEPARATOR, IOHelper.CROSS_SEPARATOR)));
IOHelper.transfer(file, compressed);
return super.visitFile(file, attrs);
}
}, true);
}
return HashedDir.WalkAction.SKIP_DIR;
}
}
return HashedDir.WalkAction.CONTINUE;
});
}
public void restart() { public void restart() {
ProcessBuilder builder = new ProcessBuilder(); ProcessBuilder builder = new ProcessBuilder();
if (config.startScript != null) builder.command(Collections.singletonList(config.startScript)); if (config.startScript != null) builder.command(Collections.singletonList(config.startScript));

View file

@ -0,0 +1,47 @@
package pro.gravit.launchserver.hasher;
import java.io.IOError;
import java.io.IOException;
import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.hasher.HashedEntry;
import pro.gravit.utils.helper.IOHelper;
public final class UpdateData {
private static final class Pair {
private int cnt = 0;
private long size = 0;
}
public static final int COUNT_LIMIT = 4;
public static final long AVG_LIMIT = IOHelper.MB*4;
public static final long TOTAL_LIMIT = IOHelper.MB*16;
public static final double SIMPLE_DOWNLOAD_SIZE_COFF = 0.75D;
public static final double SIMPLE_DOWNLOAD_FILE_COPF = 0.5D;
private UpdateData() {
}
public static boolean needsZip(Pair p) {
long avg = p.size/(long)p.cnt;
return p.size < TOTAL_LIMIT && avg < AVG_LIMIT && p.cnt > COUNT_LIMIT;
}
public static boolean needsZip(HashedDir hDir) {
return needsZip(count(hDir));
}
private static Pair count(HashedDir hDir) {
final Pair pair = new Pair();
try {
hDir.walk(IOHelper.CROSS_SEPARATOR, (p,n,e) -> {
if (e.getType().equals(HashedEntry.Type.FILE)) {
pair.cnt++;
pair.size += e.size();
}
return HashedDir.WalkAction.CONTINUE;
});
} catch (IOException e) {
throw new IOError(e); // never happen
}
return pair;
}
}

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.socket; package pro.gravit.launchserver.socket;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Arrays;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
@ -63,7 +64,7 @@ public void initChannel(SocketChannel ch) {
pipeline.addLast(new WebSocketServerCompressionHandler()); pipeline.addLast(new WebSocketServerCompressionHandler());
pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true)); pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true));
if (server.config.netty.fileServerEnabled) if (server.config.netty.fileServerEnabled)
pipeline.addLast(new FileServerHandler(server.updatesDir, true)); pipeline.addLast(new FileServerHandler(Arrays.asList(server.updatesDir, server.optimizedUpdatesDir), server.config.netty.directoryListing));
pipeline.addLast(new WebSocketFrameHandler(context, server, service)); pipeline.addLast(new WebSocketFrameHandler(context, server, service));
} }
}); });

View file

@ -22,7 +22,9 @@
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -48,6 +50,7 @@
import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.stream.ChunkedFile; import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import pro.gravit.utils.helper.IOHelper;
public class FileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { public class FileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@ -56,10 +59,10 @@ public class FileServerHandler extends SimpleChannelInboundHandler<FullHttpReque
public static final String READ = "r"; public static final String READ = "r";
public static final int HTTP_CACHE_SECONDS = 60; public static final int HTTP_CACHE_SECONDS = 60;
private static final boolean OLD_ALGO = Boolean.parseBoolean(System.getProperty("launcher.fileserver.oldalgo", "true")); private static final boolean OLD_ALGO = Boolean.parseBoolean(System.getProperty("launcher.fileserver.oldalgo", "true"));
private final Path base; private final List<Path> base;
private final boolean fullOut; private final boolean fullOut;
public FileServerHandler(Path base, boolean fullOut) { public FileServerHandler(List<Path> base, boolean fullOut) {
this.base = base; this.base = base;
this.fullOut = fullOut; this.fullOut = fullOut;
} }
@ -83,12 +86,12 @@ public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) thr
return; return;
} }
File file = base.resolve(path).toFile(); Optional<File> fileO = base.stream().map(t -> t.resolve(path)).filter(t -> IOHelper.exists(t) && !IOHelper.isHidden(t)).map(t -> t.toFile()).findFirst();
if (file.isHidden() || !file.exists()) { if (!fileO.isPresent()) {
sendError(ctx, NOT_FOUND); sendError(ctx, NOT_FOUND);
return; return;
} }
File file = fileO.get();
if (file.isDirectory()) { if (file.isDirectory()) {
if (fullOut) { if (fullOut) {
if (uri.endsWith("/")) { if (uri.endsWith("/")) {
@ -172,8 +175,6 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
} }
} }
private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
private static String sanitizeUri(String uri) { private static String sanitizeUri(String uri) {
// Decode the path. // Decode the path.
try { try {

View file

@ -203,10 +203,6 @@ function goAuth(event) {
} }
var rsaPassword = null; var rsaPassword = null;
var auth = authOptions.getSelectionModel().getSelectedItem();
if (auth === null) {
return;
}
if (!passwordField.isDisable()) { if (!passwordField.isDisable()) {
var password = passwordField.getText(); var password = passwordField.getText();
if (password !== null && !password.isEmpty()) { if (password !== null && !password.isEmpty()) {

View file

@ -168,6 +168,7 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOExce
private static final Pattern CROSS_SEPARATOR_PATTERN = Pattern.compile(CROSS_SEPARATOR, Pattern.LITERAL); private static final Pattern CROSS_SEPARATOR_PATTERN = Pattern.compile(CROSS_SEPARATOR, Pattern.LITERAL);
private static final Pattern PLATFORM_SEPARATOR_PATTERN = Pattern.compile(PLATFORM_SEPARATOR, Pattern.LITERAL); private static final Pattern PLATFORM_SEPARATOR_PATTERN = Pattern.compile(PLATFORM_SEPARATOR, Pattern.LITERAL);
public static final int MB = 1 << 20;
@LauncherAPI @LauncherAPI
public static void close(AutoCloseable closeable) { public static void close(AutoCloseable closeable) {
@ -767,6 +768,15 @@ public static void write(Path file, byte[] bytes) throws IOException {
Files.write(file, bytes, WRITE_OPTIONS); Files.write(file, bytes, WRITE_OPTIONS);
} }
@LauncherAPI
public static boolean isHidden(Path path) {
try {
return Files.isHidden(path);
} catch (IOException e) {
return false;
}
}
private IOHelper() { private IOHelper() {
} }
} }