437 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			437 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package com.mojang.realmsclient.client;
 | |
| 
 | |
| import com.google.common.hash.Hashing;
 | |
| import com.google.common.io.Files;
 | |
| import com.mojang.logging.LogUtils;
 | |
| import com.mojang.realmsclient.dto.WorldDownload;
 | |
| import com.mojang.realmsclient.exception.RealmsDefaultUncaughtExceptionHandler;
 | |
| import com.mojang.realmsclient.gui.screens.RealmsDownloadLatestWorldScreen;
 | |
| import java.awt.event.ActionEvent;
 | |
| import java.awt.event.ActionListener;
 | |
| import java.io.BufferedInputStream;
 | |
| import java.io.File;
 | |
| import java.io.FileInputStream;
 | |
| import java.io.FileOutputStream;
 | |
| import java.io.IOException;
 | |
| import java.io.OutputStream;
 | |
| import java.util.Locale;
 | |
| import java.util.regex.Matcher;
 | |
| import java.util.regex.Pattern;
 | |
| import net.fabricmc.api.EnvType;
 | |
| import net.fabricmc.api.Environment;
 | |
| import net.minecraft.SharedConstants;
 | |
| import net.minecraft.client.Minecraft;
 | |
| import net.minecraft.nbt.NbtException;
 | |
| import net.minecraft.nbt.ReportedNbtException;
 | |
| import net.minecraft.world.level.storage.LevelStorageSource;
 | |
| import net.minecraft.world.level.validation.ContentValidationException;
 | |
| import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 | |
| import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
 | |
| import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
 | |
| import org.apache.commons.io.FileUtils;
 | |
| import org.apache.commons.io.IOUtils;
 | |
| import org.apache.commons.io.output.CountingOutputStream;
 | |
| import org.apache.commons.lang3.StringUtils;
 | |
| import org.apache.http.HttpResponse;
 | |
| import org.apache.http.client.config.RequestConfig;
 | |
| import org.apache.http.client.methods.CloseableHttpResponse;
 | |
| import org.apache.http.client.methods.HttpGet;
 | |
| import org.apache.http.impl.client.CloseableHttpClient;
 | |
| import org.apache.http.impl.client.HttpClientBuilder;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| @Environment(EnvType.CLIENT)
 | |
| public class FileDownload {
 | |
| 	static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	volatile boolean cancelled;
 | |
| 	volatile boolean finished;
 | |
| 	volatile boolean error;
 | |
| 	volatile boolean extracting;
 | |
| 	@Nullable
 | |
| 	private volatile File tempFile;
 | |
| 	volatile File resourcePackPath;
 | |
| 	@Nullable
 | |
| 	private volatile HttpGet request;
 | |
| 	@Nullable
 | |
| 	private Thread currentThread;
 | |
| 	private final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(120000).setConnectTimeout(120000).build();
 | |
| 	private static final String[] INVALID_FILE_NAMES = new String[]{
 | |
| 		"CON",
 | |
| 		"COM",
 | |
| 		"PRN",
 | |
| 		"AUX",
 | |
| 		"CLOCK$",
 | |
| 		"NUL",
 | |
| 		"COM1",
 | |
| 		"COM2",
 | |
| 		"COM3",
 | |
| 		"COM4",
 | |
| 		"COM5",
 | |
| 		"COM6",
 | |
| 		"COM7",
 | |
| 		"COM8",
 | |
| 		"COM9",
 | |
| 		"LPT1",
 | |
| 		"LPT2",
 | |
| 		"LPT3",
 | |
| 		"LPT4",
 | |
| 		"LPT5",
 | |
| 		"LPT6",
 | |
| 		"LPT7",
 | |
| 		"LPT8",
 | |
| 		"LPT9"
 | |
| 	};
 | |
| 
 | |
| 	public long contentLength(String uri) {
 | |
| 		CloseableHttpClient closeableHttpClient = null;
 | |
| 		HttpGet httpGet = null;
 | |
| 
 | |
| 		long var5;
 | |
| 		try {
 | |
| 			httpGet = new HttpGet(uri);
 | |
| 			closeableHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(this.requestConfig).build();
 | |
| 			CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet);
 | |
| 			return Long.parseLong(closeableHttpResponse.getFirstHeader("Content-Length").getValue());
 | |
| 		} catch (Throwable var16) {
 | |
| 			LOGGER.error("Unable to get content length for download");
 | |
| 			var5 = 0L;
 | |
| 		} finally {
 | |
| 			if (httpGet != null) {
 | |
| 				httpGet.releaseConnection();
 | |
| 			}
 | |
| 
 | |
| 			if (closeableHttpClient != null) {
 | |
| 				try {
 | |
| 					closeableHttpClient.close();
 | |
| 				} catch (IOException var15) {
 | |
| 					LOGGER.error("Could not close http client", (Throwable)var15);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return var5;
 | |
| 	}
 | |
| 
 | |
| 	public void download(WorldDownload download, String worldName, RealmsDownloadLatestWorldScreen.DownloadStatus status, LevelStorageSource source) {
 | |
| 		if (this.currentThread == null) {
 | |
| 			this.currentThread = new Thread(
 | |
| 				() -> {
 | |
| 					CloseableHttpClient closeableHttpClient = null;
 | |
| 
 | |
| 					try {
 | |
| 						this.tempFile = File.createTempFile("backup", ".tar.gz");
 | |
| 						this.request = new HttpGet(download.downloadLink);
 | |
| 						closeableHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(this.requestConfig).build();
 | |
| 						HttpResponse httpResponse = closeableHttpClient.execute(this.request);
 | |
| 						status.totalBytes = Long.parseLong(httpResponse.getFirstHeader("Content-Length").getValue());
 | |
| 						if (httpResponse.getStatusLine().getStatusCode() == 200) {
 | |
| 							OutputStream outputStream2 = new FileOutputStream(this.tempFile);
 | |
| 							FileDownload.ProgressListener progressListener = new FileDownload.ProgressListener(worldName.trim(), this.tempFile, source, status);
 | |
| 							FileDownload.DownloadCountingOutputStream downloadCountingOutputStream2 = new FileDownload.DownloadCountingOutputStream(outputStream2);
 | |
| 							downloadCountingOutputStream2.setListener(progressListener);
 | |
| 							IOUtils.copy(httpResponse.getEntity().getContent(), downloadCountingOutputStream2);
 | |
| 							return;
 | |
| 						}
 | |
| 
 | |
| 						this.error = true;
 | |
| 						this.request.abort();
 | |
| 					} catch (Exception var93) {
 | |
| 						LOGGER.error("Caught exception while downloading: {}", var93.getMessage());
 | |
| 						this.error = true;
 | |
| 						return;
 | |
| 					} finally {
 | |
| 						this.request.releaseConnection();
 | |
| 						if (this.tempFile != null) {
 | |
| 							this.tempFile.delete();
 | |
| 						}
 | |
| 
 | |
| 						if (!this.error) {
 | |
| 							if (!download.resourcePackUrl.isEmpty() && !download.resourcePackHash.isEmpty()) {
 | |
| 								try {
 | |
| 									this.tempFile = File.createTempFile("resources", ".tar.gz");
 | |
| 									this.request = new HttpGet(download.resourcePackUrl);
 | |
| 									HttpResponse httpResponse3 = closeableHttpClient.execute(this.request);
 | |
| 									status.totalBytes = Long.parseLong(httpResponse3.getFirstHeader("Content-Length").getValue());
 | |
| 									if (httpResponse3.getStatusLine().getStatusCode() != 200) {
 | |
| 										this.error = true;
 | |
| 										this.request.abort();
 | |
| 										return;
 | |
| 									}
 | |
| 
 | |
| 									OutputStream outputStream3 = new FileOutputStream(this.tempFile);
 | |
| 									FileDownload.ResourcePackProgressListener resourcePackProgressListener3 = new FileDownload.ResourcePackProgressListener(
 | |
| 										this.tempFile, status, download
 | |
| 									);
 | |
| 									FileDownload.DownloadCountingOutputStream downloadCountingOutputStream3 = new FileDownload.DownloadCountingOutputStream(outputStream3);
 | |
| 									downloadCountingOutputStream3.setListener(resourcePackProgressListener3);
 | |
| 									IOUtils.copy(httpResponse3.getEntity().getContent(), downloadCountingOutputStream3);
 | |
| 								} catch (Exception var91) {
 | |
| 									LOGGER.error("Caught exception while downloading: {}", var91.getMessage());
 | |
| 									this.error = true;
 | |
| 								} finally {
 | |
| 									this.request.releaseConnection();
 | |
| 									if (this.tempFile != null) {
 | |
| 										this.tempFile.delete();
 | |
| 									}
 | |
| 								}
 | |
| 							} else {
 | |
| 								this.finished = true;
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						if (closeableHttpClient != null) {
 | |
| 							try {
 | |
| 								closeableHttpClient.close();
 | |
| 							} catch (IOException var90) {
 | |
| 								LOGGER.error("Failed to close Realms download client");
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			);
 | |
| 			this.currentThread.setUncaughtExceptionHandler(new RealmsDefaultUncaughtExceptionHandler(LOGGER));
 | |
| 			this.currentThread.start();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void cancel() {
 | |
| 		if (this.request != null) {
 | |
| 			this.request.abort();
 | |
| 		}
 | |
| 
 | |
| 		if (this.tempFile != null) {
 | |
| 			this.tempFile.delete();
 | |
| 		}
 | |
| 
 | |
| 		this.cancelled = true;
 | |
| 	}
 | |
| 
 | |
| 	public boolean isFinished() {
 | |
| 		return this.finished;
 | |
| 	}
 | |
| 
 | |
| 	public boolean isError() {
 | |
| 		return this.error;
 | |
| 	}
 | |
| 
 | |
| 	public boolean isExtracting() {
 | |
| 		return this.extracting;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Modifies a folder name to make sure it is valid to store on disk.
 | |
| 	 * @return the modified folder name
 | |
| 	 * 
 | |
| 	 * @param folderName The folder name to modify
 | |
| 	 */
 | |
| 	public static String findAvailableFolderName(String folderName) {
 | |
| 		folderName = folderName.replaceAll("[\\./\"]", "_");
 | |
| 
 | |
| 		for (String string : INVALID_FILE_NAMES) {
 | |
| 			if (folderName.equalsIgnoreCase(string)) {
 | |
| 				folderName = "_" + folderName + "_";
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return folderName;
 | |
| 	}
 | |
| 
 | |
| 	void untarGzipArchive(String worldName, @Nullable File tempFile, LevelStorageSource levelStorageSource) throws IOException {
 | |
| 		Pattern pattern = Pattern.compile(".*-([0-9]+)$");
 | |
| 		int i = 1;
 | |
| 
 | |
| 		for (char c : SharedConstants.ILLEGAL_FILE_CHARACTERS) {
 | |
| 			worldName = worldName.replace(c, '_');
 | |
| 		}
 | |
| 
 | |
| 		if (StringUtils.isEmpty(worldName)) {
 | |
| 			worldName = "Realm";
 | |
| 		}
 | |
| 
 | |
| 		worldName = findAvailableFolderName(worldName);
 | |
| 
 | |
| 		try {
 | |
| 			for (LevelStorageSource.LevelDirectory levelDirectory : levelStorageSource.findLevelCandidates()) {
 | |
| 				String string = levelDirectory.directoryName();
 | |
| 				if (string.toLowerCase(Locale.ROOT).startsWith(worldName.toLowerCase(Locale.ROOT))) {
 | |
| 					Matcher matcher = pattern.matcher(string);
 | |
| 					if (matcher.matches()) {
 | |
| 						int j = Integer.parseInt(matcher.group(1));
 | |
| 						if (j > i) {
 | |
| 							i = j;
 | |
| 						}
 | |
| 					} else {
 | |
| 						i++;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		} catch (Exception var43) {
 | |
| 			LOGGER.error("Error getting level list", (Throwable)var43);
 | |
| 			this.error = true;
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		String string2;
 | |
| 		if (levelStorageSource.isNewLevelIdAcceptable(worldName) && i <= 1) {
 | |
| 			string2 = worldName;
 | |
| 		} else {
 | |
| 			string2 = worldName + (i == 1 ? "" : "-" + i);
 | |
| 			if (!levelStorageSource.isNewLevelIdAcceptable(string2)) {
 | |
| 				boolean bl = false;
 | |
| 
 | |
| 				while (!bl) {
 | |
| 					i++;
 | |
| 					string2 = worldName + (i == 1 ? "" : "-" + i);
 | |
| 					if (levelStorageSource.isNewLevelIdAcceptable(string2)) {
 | |
| 						bl = true;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		TarArchiveInputStream tarArchiveInputStream = null;
 | |
| 		File file = new File(Minecraft.getInstance().gameDirectory.getAbsolutePath(), "saves");
 | |
| 
 | |
| 		try {
 | |
| 			file.mkdir();
 | |
| 			tarArchiveInputStream = new TarArchiveInputStream(new GzipCompressorInputStream(new BufferedInputStream(new FileInputStream(tempFile))));
 | |
| 
 | |
| 			for (TarArchiveEntry tarArchiveEntry = tarArchiveInputStream.getNextTarEntry();
 | |
| 				tarArchiveEntry != null;
 | |
| 				tarArchiveEntry = tarArchiveInputStream.getNextTarEntry()
 | |
| 			) {
 | |
| 				File file2 = new File(file, tarArchiveEntry.getName().replace("world", string2));
 | |
| 				if (tarArchiveEntry.isDirectory()) {
 | |
| 					file2.mkdirs();
 | |
| 				} else {
 | |
| 					file2.createNewFile();
 | |
| 					FileOutputStream fileOutputStream = new FileOutputStream(file2);
 | |
| 
 | |
| 					try {
 | |
| 						IOUtils.copy(tarArchiveInputStream, fileOutputStream);
 | |
| 					} catch (Throwable var37) {
 | |
| 						try {
 | |
| 							fileOutputStream.close();
 | |
| 						} catch (Throwable var36) {
 | |
| 							var37.addSuppressed(var36);
 | |
| 						}
 | |
| 
 | |
| 						throw var37;
 | |
| 					}
 | |
| 
 | |
| 					fileOutputStream.close();
 | |
| 				}
 | |
| 			}
 | |
| 		} catch (Exception var41) {
 | |
| 			LOGGER.error("Error extracting world", (Throwable)var41);
 | |
| 			this.error = true;
 | |
| 		} finally {
 | |
| 			if (tarArchiveInputStream != null) {
 | |
| 				tarArchiveInputStream.close();
 | |
| 			}
 | |
| 
 | |
| 			if (tempFile != null) {
 | |
| 				tempFile.delete();
 | |
| 			}
 | |
| 
 | |
| 			try (LevelStorageSource.LevelStorageAccess levelStorageAccess2 = levelStorageSource.validateAndCreateAccess(string2)) {
 | |
| 				levelStorageAccess2.renameAndDropPlayer(string2);
 | |
| 			} catch (NbtException | ReportedNbtException | IOException var39) {
 | |
| 				LOGGER.error("Failed to modify unpacked realms level {}", string2, var39);
 | |
| 			} catch (ContentValidationException var40) {
 | |
| 				LOGGER.warn("{}", var40.getMessage());
 | |
| 			}
 | |
| 
 | |
| 			this.resourcePackPath = new File(file, string2 + File.separator + "resources.zip");
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	static class DownloadCountingOutputStream extends CountingOutputStream {
 | |
| 		@Nullable
 | |
| 		private ActionListener listener;
 | |
| 
 | |
| 		public DownloadCountingOutputStream(OutputStream out) {
 | |
| 			super(out);
 | |
| 		}
 | |
| 
 | |
| 		public void setListener(ActionListener listener) {
 | |
| 			this.listener = listener;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		protected void afterWrite(int i) throws IOException {
 | |
| 			super.afterWrite(i);
 | |
| 			if (this.listener != null) {
 | |
| 				this.listener.actionPerformed(new ActionEvent(this, 0, null));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	class ProgressListener implements ActionListener {
 | |
| 		private final String worldName;
 | |
| 		private final File tempFile;
 | |
| 		private final LevelStorageSource levelStorageSource;
 | |
| 		private final RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus;
 | |
| 
 | |
| 		ProgressListener(
 | |
| 			final String worldName,
 | |
| 			final File tempFile,
 | |
| 			final LevelStorageSource levelStorageSource,
 | |
| 			final RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus
 | |
| 		) {
 | |
| 			this.worldName = worldName;
 | |
| 			this.tempFile = tempFile;
 | |
| 			this.levelStorageSource = levelStorageSource;
 | |
| 			this.downloadStatus = downloadStatus;
 | |
| 		}
 | |
| 
 | |
| 		public void actionPerformed(ActionEvent actionEvent) {
 | |
| 			this.downloadStatus.bytesWritten = ((FileDownload.DownloadCountingOutputStream)actionEvent.getSource()).getByteCount();
 | |
| 			if (this.downloadStatus.bytesWritten >= this.downloadStatus.totalBytes && !FileDownload.this.cancelled && !FileDownload.this.error) {
 | |
| 				try {
 | |
| 					FileDownload.this.extracting = true;
 | |
| 					FileDownload.this.untarGzipArchive(this.worldName, this.tempFile, this.levelStorageSource);
 | |
| 				} catch (IOException var3) {
 | |
| 					FileDownload.LOGGER.error("Error extracting archive", (Throwable)var3);
 | |
| 					FileDownload.this.error = true;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	class ResourcePackProgressListener implements ActionListener {
 | |
| 		private final File tempFile;
 | |
| 		private final RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus;
 | |
| 		private final WorldDownload worldDownload;
 | |
| 
 | |
| 		ResourcePackProgressListener(final File tempFile, final RealmsDownloadLatestWorldScreen.DownloadStatus downloadStatus, final WorldDownload worldDownload) {
 | |
| 			this.tempFile = tempFile;
 | |
| 			this.downloadStatus = downloadStatus;
 | |
| 			this.worldDownload = worldDownload;
 | |
| 		}
 | |
| 
 | |
| 		public void actionPerformed(ActionEvent actionEvent) {
 | |
| 			this.downloadStatus.bytesWritten = ((FileDownload.DownloadCountingOutputStream)actionEvent.getSource()).getByteCount();
 | |
| 			if (this.downloadStatus.bytesWritten >= this.downloadStatus.totalBytes && !FileDownload.this.cancelled) {
 | |
| 				try {
 | |
| 					String string = Hashing.sha1().hashBytes(Files.toByteArray(this.tempFile)).toString();
 | |
| 					if (string.equals(this.worldDownload.resourcePackHash)) {
 | |
| 						FileUtils.copyFile(this.tempFile, FileDownload.this.resourcePackPath);
 | |
| 						FileDownload.this.finished = true;
 | |
| 					} else {
 | |
| 						FileDownload.LOGGER.error("Resourcepack had wrong hash (expected {}, found {}). Deleting it.", this.worldDownload.resourcePackHash, string);
 | |
| 						FileUtils.deleteQuietly(this.tempFile);
 | |
| 						FileDownload.this.error = true;
 | |
| 					}
 | |
| 				} catch (IOException var3) {
 | |
| 					FileDownload.LOGGER.error("Error copying resourcepack file: {}", var3.getMessage());
 | |
| 					FileDownload.this.error = true;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |