UpdateModule class
Пакет: com.hypixel.hytale.server.core.update
Файл: com/hypixel/hytale/server/core/update/UpdateModule.java
extends: JavaPlugin
Поля (54)
| Модификаторы | Тип | Имя |
|---|---|---|
|
|
break |
|
|
instance |
|
Thread |
var1 |
|
String |
var1 |
|
UpdateConfig |
var1 |
|
String |
var1 |
|
long |
var1 |
|
CompletableFuture |
var1 |
|
UpdateConfig |
var1 |
|
ServerAuthManager |
var1 |
|
String |
var1 |
|
long |
var10 |
|
|
var10 |
|
Instant |
var10 |
|
long |
var11 |
|
double |
var12 |
|
long |
var13 |
|
long |
var14 |
|
long |
var15 |
|
long |
var17 |
|
UpdateConfig |
var2 |
|
UpdateConfig.AutoApplyMode |
var2 |
|
Thread |
var2 |
|
ServerAuthManager |
var2 |
|
String |
var2 |
|
UpdateService |
var2 |
|
UpdateConfig |
var2 |
|
Universe |
var2 |
|
Universe |
var2 |
|
int |
var3 |
|
long |
var3 |
|
String |
var3 |
|
String |
var3 |
|
String |
var3 |
|
String |
var3 |
|
UpdateService.DownloadTask |
var3 |
|
UpdateConfig |
var3 |
|
String |
var3 |
|
UpdateConfig.AutoApplyMode |
var3 |
|
Message |
var3 |
|
UpdateService.PatchlineSummary[] |
var4 |
|
String |
var4 |
|
UpdateConfig |
var4 |
|
CompletableFuture |
var4 |
|
Universe |
var4 |
|
PermissionsModule |
var4 |
|
long |
var5 |
|
String |
var5 |
|
int |
var5 |
|
int |
var6 |
|
int |
var7 |
|
long |
var7 |
|
long |
var8 |
|
long |
var9 |
Методы (51)
| Модификаторы | Возврат | Сигнатура |
|---|---|---|
public |
record |
DownloadProgressrecord DownloadProgress(int percent, long downloadedBytes, long totalBytes, long etaSeconds) |
private |
void |
autoDownloadUpdatevoid autoDownloadUpdate(@Nonnull UpdateService var1, @Nonnull UpdateService.VersionManifest var2) |
private |
void |
broadcastToPlayersvoid broadcastToPlayers(@Nonnull Message var1) |
public |
boolean |
cancelDownloadboolean cancelDownload() |
private |
void |
checkAutoApplyvoid checkAutoApply(@Nonnull String var1) |
private |
boolean |
checkPatchlineExpiryboolean checkPatchlineExpiry(UpdateService var1) |
|
|
for for(UpdateService.PatchlineSummary var9 : var4) |
|
|
if if(KILL_SWITCH_ENABLED) |
|
|
if if(!KILL_SWITCH_ENABLED) |
|
|
if if(var1 != null) |
|
|
if if(this.autoApplyTask == null) |
|
|
if if(var2 != UpdateConfig.AutoApplyMode.DISABLED) |
|
|
if if(this.updateCheckTask != null) |
|
|
if if(this.autoApplyTask != null) |
|
|
if if(!KILL_SWITCH_ENABLED) |
|
|
if if(var1 != null) |
|
|
if if(var1 != 0L && var5 > 0L) |
|
|
if if(var8 > 0L && var3 > 0L) |
|
|
if if(var2 != null) |
|
|
if if(var1 == null && var2 == null) |
|
|
if if(var1 != null) |
|
|
if if(var3 == null) |
|
|
if if(var4 == null) |
|
|
if if(Constants.SINGLEPLAYER) |
|
|
if if(!var1x) |
|
|
if if(var3 != null) |
|
|
if if(var2x == null) |
|
|
if if(var1 == null) |
|
|
if if(var2) |
|
|
if if(var3 != UpdateConfig.AutoApplyMode.DISABLED) |
|
|
if if(var4 != null) |
|
|
if if(var5 == 0) |
|
|
if if(var7 >= var11 - 2000L) |
|
|
if if(var2 != null) |
|
|
if if(var2 != null) |
public |
boolean |
isDownloadInProgressboolean isDownloadInProgress() |
private |
void |
logStagedUpdateWarningvoid logStagedUpdateWarning(@Nullable String var1, boolean var2) |
private |
void |
logUpdateAvailablevoid logUpdateAvailable(@Nullable String var1, @Nonnull String var2) |
private |
void |
notifyPlayersvoid notifyPlayers(@Nonnull String var1) |
public |
void |
onServerReadyvoid onServerReady() |
private |
void |
performAutoApplyCheckvoid performAutoApplyCheck() |
private |
void |
performUpdateCheckvoid performUpdateCheck() |
public |
void |
releaseDownloadLockvoid releaseDownloadLock() |
public |
void |
setActiveDownloadvoid setActiveDownload(@Nullable CompletableFuture<?> var1, @Nullable Thread var2) |
public |
void |
setLatestKnownVersionvoid setLatestKnownVersion(@Nullable UpdateService.VersionManifest var1) |
private |
boolean |
shouldEnableUpdateCheckerboolean shouldEnableUpdateChecker() |
synchronized |
void |
startAutoApplyTaskIfNeededvoid startAutoApplyTaskIfNeeded() |
abstract |
|
super super(var1) |
private |
void |
triggerAutoApplyvoid triggerAutoApply() |
public |
boolean |
tryAcquireDownloadLockboolean tryAcquireDownloadLock() |
public |
void |
updateDownloadProgressvoid updateDownloadProgress(long var1, long var3) |
Исходный код
Показать/скрыть
class="kw">package com.hypixel.hytale.server.core.update;
class="kw">import com.hypixel.hytale.common.plugin.PluginManifest;
class="kw">import com.hypixel.hytale.common.util.SystemUtil;
class="kw">import com.hypixel.hytale.common.util.java.ManifestUtil;
class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.server.core.Constants;
class="kw">import com.hypixel.hytale.server.core.HytaleServer;
class="kw">import com.hypixel.hytale.server.core.Message;
class="kw">import com.hypixel.hytale.server.core.ShutdownReason;
class="kw">import com.hypixel.hytale.server.core.auth.ServerAuthManager;
class="kw">import com.hypixel.hytale.server.core.config.UpdateConfig;
class="kw">import com.hypixel.hytale.server.core.permissions.HytalePermissions;
class="kw">import com.hypixel.hytale.server.core.permissions.PermissionsModule;
class="kw">import com.hypixel.hytale.server.core.plugin.JavaPlugin;
class="kw">import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
class="kw">import com.hypixel.hytale.server.core.universe.PlayerRef;
class="kw">import com.hypixel.hytale.server.core.universe.Universe;
class="kw">import com.hypixel.hytale.server.core.update.command.UpdateCommand;
class="kw">import java.awt.Color;
class="kw">import java.io.IOException;
class="kw">import java.time.Instant;
class="kw">import java.util.concurrent.CancellationException;
class="kw">import java.util.concurrent.CompletableFuture;
class="kw">import java.util.concurrent.Executors;
class="kw">import java.util.concurrent.ScheduledExecutorService;
class="kw">import java.util.concurrent.ScheduledFuture;
class="kw">import java.util.concurrent.TimeUnit;
class="kw">import java.util.concurrent.atomic.AtomicBoolean;
class="kw">import java.util.concurrent.atomic.AtomicLong;
class="kw">import java.util.concurrent.atomic.AtomicReference;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;
class="kw">public class UpdateModule class="kw">extends JavaPlugin {
@Nonnull
class="kw">public class="kw">static class="kw">final PluginManifest MANIFEST = PluginManifest.corePlugin(UpdateModule.class).build();
@Nonnull
class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
class="kw">public class="kw">static class="kw">final boolean KILL_SWITCH_ENABLED = SystemUtil.getEnvBoolean("HYTALE_DISABLE_UPDATES");
class="kw">private class="kw">static UpdateModule instance;
@Nonnull
class="kw">private class="kw">final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(var0 -> {
Thread var1 = new Thread(var0, "UpdateChecker");
var1.setDaemon(true);
class="kw">return var1;
});
@Nullable
class="kw">private ScheduledFuture<?> updateCheckTask;
@Nullable
class="kw">private ScheduledFuture<?> autoApplyTask;
class="kw">private class="kw">final AtomicReference<UpdateService.VersionManifest> latestKnownVersion = new AtomicReference<>();
class="kw">private class="kw">final AtomicReference<CompletableFuture<?>> activeDownload = new AtomicReference<>();
class="kw">private class="kw">final AtomicReference<Thread> activeDownloadThread = new AtomicReference<>();
class="kw">private class="kw">final AtomicBoolean downloadLock = new AtomicBoolean(false);
class="kw">private class="kw">final AtomicLong downloadStartTime = new AtomicLong(0L);
class="kw">private class="kw">final AtomicLong downloadedBytes = new AtomicLong(0L);
class="kw">private class="kw">final AtomicLong totalBytes = new AtomicLong(0L);
class="kw">private class="kw">final AtomicLong autoApplyScheduledTime = new AtomicLong(0L);
class="kw">private class="kw">final AtomicLong lastWarningTime = new AtomicLong(0L);
class="kw">private class="kw">final AtomicBoolean patchlineExpiryChecked = new AtomicBoolean(false);
class="kw">public UpdateModule(@Nonnull JavaPluginInit var1) {
super(var1);
instance = this;
}
@Nullable
class="kw">public class="kw">static UpdateModule get() {
class="kw">return instance;
}
@Override
class="kw">protected void setup() {
if (KILL_SWITCH_ENABLED) {
LOGGER.at(Level.INFO).log("Update commands disabled via HYTALE_DISABLE_UPDATES environment variable");
}
this.getCommandRegistry().registerCommand(new UpdateCommand());
}
@Override
class="kw">protected void start() {
if (!KILL_SWITCH_ENABLED) {
String var1 = UpdateService.getStagedVersion();
if (var1 != null) {
this.logStagedUpdateWarning(var1, true);
this.startAutoApplyTaskIfNeeded();
}
if (this.shouldEnableUpdateChecker()) {
UpdateConfig var2 = HytaleServer.get().getConfig().getUpdateConfig();
int var3 = var2.getCheckIntervalSeconds();
LOGGER.at(Level.INFO).log("Update checker enabled (interval: %ds)", var3);
this.updateCheckTask = this.scheduler.scheduleAtFixedRate(this::performUpdateCheck, 60L, var3, TimeUnit.SECONDS);
}
}
}
class="kw">private class="kw">synchronized void startAutoApplyTaskIfNeeded() {
if (this.autoApplyTask == null) {
UpdateConfig var1 = HytaleServer.get().getConfig().getUpdateConfig();
UpdateConfig.AutoApplyMode var2 = var1.getAutoApplyMode();
if (var2 != UpdateConfig.AutoApplyMode.DISABLED) {
LOGGER.at(Level.INFO).log("Starting auto-apply task (mode: %s, delay: %d min)", var2, var1.getAutoApplyDelayMinutes());
this.autoApplyTask = this.scheduler.scheduleAtFixedRate(this::performAutoApplyCheck, 0L, 60L, TimeUnit.SECONDS);
}
}
}
@Override
class="kw">protected void shutdown() {
if (this.updateCheckTask != null) {
this.updateCheckTask.cancel(false);
}
if (this.autoApplyTask != null) {
this.autoApplyTask.cancel(false);
}
this.scheduler.shutdown();
}
class="kw">public void onServerReady() {
if (!KILL_SWITCH_ENABLED) {
String var1 = UpdateService.getStagedVersion();
if (var1 != null) {
this.logStagedUpdateWarning(var1, false);
}
}
}
@Nullable
class="kw">public UpdateService.VersionManifest getLatestKnownVersion() {
class="kw">return this.latestKnownVersion.get();
}
class="kw">public void setLatestKnownVersion(@Nullable UpdateService.VersionManifest var1) {
this.latestKnownVersion.set(var1);
}
class="kw">public boolean isDownloadInProgress() {
class="kw">return this.downloadLock.get();
}
class="kw">public boolean tryAcquireDownloadLock() {
class="kw">return this.downloadLock.compareAndSet(false, true);
}
class="kw">public void setActiveDownload(@Nullable CompletableFuture<?> var1, @Nullable Thread var2) {
this.activeDownload.set(var1);
this.activeDownloadThread.set(var2);
}
class="kw">public void releaseDownloadLock() {
this.activeDownload.set(null);
this.activeDownloadThread.set(null);
this.downloadLock.set(false);
this.downloadStartTime.set(0L);
this.downloadedBytes.set(0L);
this.totalBytes.set(0L);
}
class="kw">public void updateDownloadProgress(long var1, long var3) {
if (this.downloadStartTime.get() == 0L) {
this.downloadStartTime.set(System.currentTimeMillis());
}
this.downloadedBytes.set(var1);
this.totalBytes.set(var3);
}
@Nullable
class="kw">public UpdateModule.DownloadProgress getDownloadProgress() {
if (!this.downloadLock.get()) {
class="kw">return null;
}
long var1 = this.downloadStartTime.get();
long var3 = this.downloadedBytes.get();
long var5 = this.totalBytes.get();
if (var1 != 0L && var5 > 0L) {
int var7 = (int)(var3 * 100L / var5);
long var8 = System.currentTimeMillis() - var1;
long var10 = -1L;
if (var8 > 0L && var3 > 0L) {
double var12 = (double)var3 / var8;
long var14 = var5 - var3;
var10 = (long)(var14 / var12 / 1000.0);
}
class="kw">return new UpdateModule.DownloadProgress(var7, var3, var5, var10);
} else {
class="kw">return new UpdateModule.DownloadProgress(0, 0L, var5, -1L);
}
}
class="kw">public boolean cancelDownload() {
CompletableFuture var1 = this.activeDownload.getAndSet(null);
Thread var2 = this.activeDownloadThread.getAndSet(null);
if (var2 != null) {
var2.interrupt();
}
if (var1 == null && var2 == null) {
class="kw">return false;
}
if (var1 != null) {
var1.cancel(true);
}
this.releaseDownloadLock();
class="kw">return true;
}
class="kw">private boolean checkPatchlineExpiry(UpdateService var1) class="kw">throws IOException, InterruptedException {
ServerAuthManager var2 = ServerAuthManager.getInstance();
String var3 = var2.getOAuthAccessToken();
if (var3 == null) {
class="kw">return false;
}
UpdateService.PatchlineSummary[] var4 = var1.fetchPatchlines(var3);
if (var4 == null) {
class="kw">return false;
}
String var5 = UpdateService.getEffectivePatchline();
for (UpdateService.PatchlineSummary var9 : var4) {
if (var9.name.equals(var5) && var9.expiresAt > 0L) {
Instant var10 = Instant.ofEpochMilli(var9.expiresAt);
if (var10.isBefore(Instant.now())) {
LOGGER.at(Level.SEVERE).log("Patchline '%s' expired at %s", var5, var10);
} else {
LOGGER.at(Level.WARNING).log("Patchline '%s' will expire at %s", var5, var10);
}
break;
}
}
class="kw">return true;
}
class="kw">private boolean shouldEnableUpdateChecker() {
if (!ManifestUtil.isJar()) {
LOGGER.at(Level.INFO).log("Update checker disabled: not running from JAR");
class="kw">return false;
}
if (Constants.SINGLEPLAYER) {
LOGGER.at(Level.INFO).log("Update checker disabled: singleplayer mode");
class="kw">return false;
}
UpdateConfig var1 = HytaleServer.get().getConfig().getUpdateConfig();
if (!var1.isEnabled()) {
LOGGER.at(Level.INFO).log("Update checker disabled: disabled in config");
class="kw">return false;
}
String var2 = ManifestUtil.getPatchline();
String var3 = var1.getPatchline();
if (!"dev".equals(var2) || var3 != null && !var3.isEmpty()) {
if (!UpdateService.isValidUpdateLayout()) {
LOGGER.at(Level.WARNING)
.log("Update checker disabled: invalid folder layout. Expected to run from Server/ with Assets.zip and start.sh/bat in parent directory.");
class="kw">return false;
} else {
class="kw">return true;
}
} else {
LOGGER.at(Level.INFO).log("Update checker disabled: dev patchline (set Patchline in config to override)");
class="kw">return false;
}
}
class="kw">private void performUpdateCheck() {
ServerAuthManager var1 = ServerAuthManager.getInstance();
if (!var1.hasSessionToken()) {
LOGGER.at(Level.FINE).log("Not authenticated - skipping update check");
} else {
UpdateService var2 = new UpdateService();
if (this.patchlineExpiryChecked.compareAndSet(false, true)) {
CompletableFuture.<Boolean>supplyAsync(() -> {
try {
class="kw">return this.checkPatchlineExpiry(var2);
} catch (InterruptedException var3) {
Thread.currentThread().interrupt();
class="kw">return false;
} catch (Exception var4) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to check patchline expiry");
class="kw">return false;
}
}).thenAccept(var1x -> {
if (!var1x) {
this.patchlineExpiryChecked.set(false);
}
});
}
String var3 = UpdateService.getStagedVersion();
if (var3 != null) {
LOGGER.at(Level.FINE).log("Staged update already exists (%s) - skipping update check", var3);
this.startAutoApplyTaskIfNeeded();
} else if (this.isDownloadInProgress()) {
LOGGER.at(Level.FINE).log("Download in progress - skipping update check");
} else {
String var4 = UpdateService.getEffectivePatchline();
var2.checkForUpdate(var4).thenAccept(var2x -> {
if (var2x == null) {
LOGGER.at(Level.FINE).log("Update check returned no result");
} else {
this.setLatestKnownVersion(var2x);
String var3 = ManifestUtil.getImplementationVersion();
if (var3 != null && var3.equals(var2x.version)) {
LOGGER.at(Level.FINE).log("Already running latest version: %s", var3);
} else {
this.logUpdateAvailable(var3, var2x.version);
UpdateConfig var4 = HytaleServer.get().getConfig().getUpdateConfig();
if (var4.isNotifyPlayersOnAvailable()) {
this.notifyPlayers(var2x.version);
}
if (var4.getAutoApplyMode() != UpdateConfig.AutoApplyMode.DISABLED) {
LOGGER.at(Level.INFO).log("Auto-downloading update %s...", var2x.version);
this.autoDownloadUpdate(var2, var2x);
}
}
}
});
}
}
}
class="kw">private void autoDownloadUpdate(@Nonnull UpdateService var1, @Nonnull UpdateService.VersionManifest var2) {
if (UpdateService.getStagedVersion() == null && this.tryAcquireDownloadLock()) {
UpdateService.DownloadTask var3 = var1.downloadUpdate(
var2, UpdateService.getStagingDir(), (var1x, var2x, var4x) -> this.updateDownloadProgress(var2x, var4x)
);
CompletableFuture var4 = var3.future().whenComplete((var2x, var3x) -> {
this.releaseDownloadLock();
if (Boolean.TRUE.equals(var2x)) {
LOGGER.at(Level.INFO).log("Update %s downloaded and staged", var2.version);
this.startAutoApplyTaskIfNeeded();
} else if (var3x class="kw">instanceof CancellationException) {
LOGGER.at(Level.INFO).log("Download of update %s was cancelled", var2.version);
UpdateService.deleteStagedUpdate();
} else {
LOGGER.at(Level.WARNING).log("Failed to download update %s: %s", var2.version, var3x != null ? var3x.getMessage() : "unknown error");
UpdateService.deleteStagedUpdate();
}
});
this.setActiveDownload(var4, var3.thread());
}
}
class="kw">private void performAutoApplyCheck() {
String var1 = UpdateService.getStagedVersion();
if (var1 == null) {
if (this.autoApplyScheduledTime.getAndSet(0L) != 0L) {
LOGGER.at(Level.FINE).log("No staged update - clearing auto-apply schedule");
}
this.lastWarningTime.set(0L);
} else {
LOGGER.at(Level.FINE).log("Auto-apply check: staged version %s", var1);
this.checkAutoApply(var1);
}
}
class="kw">private void logUpdateAvailable(@Nullable String var1, @Nonnull String var2) {
LOGGER.at(Level.INFO).log("Update available: %s (current: %s)", var2, var1);
UpdateConfig var3 = HytaleServer.get().getConfig().getUpdateConfig();
if (var3.getAutoApplyMode() == UpdateConfig.AutoApplyMode.DISABLED) {
LOGGER.at(Level.INFO).log("Run '/update download' to stage the update");
}
}
class="kw">private void logStagedUpdateWarning(@Nullable String var1, boolean var2) {
String var3 = "\u001b[0;33m===============================================================================================";
LOGGER.at(Level.INFO).log("%s", var3);
if (var2) {
LOGGER.at(Level.INFO).log("%s WARNING: Staged update %s not applied!", "\u001b[0;33m", var1 != null ? var1 : "unknown");
LOGGER.at(Level.INFO).log("%s Use launcher script (start.sh/bat) or manually move files from updater/staging/", "\u001b[0;33m");
} else {
LOGGER.at(Level.INFO).log("%s REMINDER: Staged update %s waiting to be applied", "\u001b[0;33m", var1 != null ? var1 : "unknown");
LOGGER.at(Level.INFO).log("%s Run '/update status' for details or '/update cancel' to abort", "\u001b[0;33m");
}
LOGGER.at(Level.INFO).log("%s", var3);
}
class="kw">private void checkAutoApply(@Nonnull String var1) {
UpdateConfig var2 = HytaleServer.get().getConfig().getUpdateConfig();
UpdateConfig.AutoApplyMode var3 = var2.getAutoApplyMode();
if (var3 != UpdateConfig.AutoApplyMode.DISABLED) {
Universe var4 = Universe.get();
if (var4 != null) {
int var5 = var4.getPlayers().size();
if (var5 == 0) {
LOGGER.at(Level.INFO).log("No players online - auto-applying update %s", var1);
this.triggerAutoApply();
} else if (var3 != UpdateConfig.AutoApplyMode.WHEN_EMPTY) {
int var6 = var2.getAutoApplyDelayMinutes();
long var7 = System.currentTimeMillis();
long var9 = var7 + var6 * 60L * 1000L;
if (this.autoApplyScheduledTime.compareAndSet(0L, var9)) {
LOGGER.at(Level.INFO).log("Update %s will be auto-applied in %d minutes (players online: %d)", var1, var6, var5);
this.broadcastToPlayers(
Message.translation("server.update.auto_apply_warning").param("version", var1).param("minutes", var6).color(Color.YELLOW)
);
} else {
long var11 = this.autoApplyScheduledTime.get();
if (var7 >= var11 - 2000L) {
LOGGER.at(Level.INFO).log("Auto-apply delay expired - applying update %s", var1);
this.broadcastToPlayers(Message.translation("server.update.auto_apply_now").param("version", var1).color(Color.RED));
this.triggerAutoApply();
} else {
long var13 = (var11 - var7) / 60000L;
long var15 = var13 <= 1L ? 30000L : 300000L;
long var17 = this.lastWarningTime.get();
if (var7 - var17 >= var15 && this.lastWarningTime.compareAndSet(var17, var7)) {
LOGGER.at(Level.INFO).log("Update %s will be auto-applied in %d minute(s)", var1, Math.max(1L, var13));
this.broadcastToPlayers(
Message.translation("server.update.auto_apply_warning")
.param("version", var1)
.param("minutes", Math.max(1L, var13))
.color(Color.YELLOW)
);
}
}
}
}
}
}
}
class="kw">private void triggerAutoApply() {
this.autoApplyScheduledTime.set(0L);
HytaleServer.get().shutdownServer(ShutdownReason.UPDATE);
}
class="kw">private void broadcastToPlayers(@Nonnull Message var1) {
Universe var2 = Universe.get();
if (var2 != null) {
for (PlayerRef var4 : var2.getPlayers()) {
var4.sendMessage(var1);
}
}
}
class="kw">private void notifyPlayers(@Nonnull String var1) {
Universe var2 = Universe.get();
if (var2 != null) {
Message var3 = Message.translation("server.update.notify_players").param("version", var1);
PermissionsModule var4 = PermissionsModule.get();
for (PlayerRef var6 : var2.getPlayers()) {
if (var4.hasPermission(var6.getUuid(), HytalePermissions.UPDATE_NOTIFY)) {
var6.sendMessage(var3);
}
}
}
}
class="kw">public record DownloadProgress(int percent, long downloadedBytes, long totalBytes, long etaSeconds) {
class="kw">public DownloadProgress {
}
}
}