Repository: hydralauncher/hydra
File: src/main/services/download/js-http-downloader.ts (TypeScript)

Code before:

  private bytesDownloaded = 0;
  private fileSize = 0;
  private downloadSpeed = 0;
  private status: "active" | "paused" | "complete" | "error" = "paused";
  private folderName = "";
  private lastSpeedUpdate = Date.now();
  private bytesAtLastSpeedUpdate = 0;
  private isDownloading = false;

  private retryCount = 0;
  private lastDataReceivedAt = Date.now();
  private stallCheckInterval: NodeJS.Timeout | null = null;
  private isPaused = false;
  private isStallRetry = false;

  async startDownload(options: JsHttpDownloaderOptions): Promise<void> {
    if (this.isDownloading) {
      logger.log(
        "[JsHttpDownloader] Download already in progress, resuming..."
      );
      return this.resumeDownload();
    }

    this.currentOptions = options;
    this.isPaused = false;
    await this.startDownloadWithRetry();
  }

  private async startDownloadWithRetry(): Promise<void> {
    if (!this.currentOptions) return;

    while (this.retryCount <= MAX_RETRY_ATTEMPTS && !this.isPaused) {
      this.abortController = new AbortController();
      this.status = "active";
      this.isDownloading = true;
      this.isStallRetry = false;
      this.lastDataReceivedAt = Date.now();

      const { url, savePath, filename, headers = {} } = this.currentOptions;
      const { filePath, startByte, usedFallback } = this.prepareDownloadPath(
        savePath,
        filename,
        url
      );
      const requestHeaders = this.buildRequestHeaders(headers, startByte);

      this.startStallDetection();

      try {
        await this.executeDownload(

Review comment (bug):
nit/bug: on a fresh `startDownload(options)` we probably want to reset `retryCount = 0` too. otherwise a previous download that hit the max retries could prevent future downloads from retrying (since `startDownloadWithRetry()` uses `retryCount` in the while condition).

Code after:

  private bytesDownloaded = 0;
  private fileSize = 0;
  private downloadSpeed = 0;
  private status: "active" | "paused" | "complete" | "error" = "paused";
  private folderName = "";
  private lastSpeedUpdate = Date.now();
  private bytesAtLastSpeedUpdate = 0;
  private isDownloading = false;

  private retryCount = 0;
  private lastDataReceivedAt = Date.now();
  private stallCheckInterval: NodeJS.Timeout | null = null;
  private isPaused = false;
  private isStallRetry = false;

  async startDownload(options: JsHttpDownloaderOptions): Promise<void> {
    if (this.isDownloading) {
      logger.log(
        "[JsHttpDownloader] Download already in progress, resuming..."
      );
      return this.resumeDownload();
    }

    this.currentOptions = options;
    this.isPaused = false;
    this.retryCount = 0;
    this.isStallRetry = false;
    await this.startDownloadWithRetry();
  }

  private async startDownloadWithRetry(): Promise<void> {
    if (!this.currentOptions) return;

    while (!this.isPaused) {
      this.abortController = new AbortController();
      this.status = "active";
      this.isDownloading = true;
      this.isStallRetry = false;
      this.lastDataReceivedAt = Date.now();

      const { url, savePath, filename, headers = {} } = this.currentOptions;
      const { filePath, startByte, usedFallback } = this.prepareDownloadPath(
        savePath,
        filename,
        url
      );
      const requestHeaders = this.buildRequestHeaders(headers, startByte);

      this.startStallDetection();