mirror of
				https://github.com/hydralauncher/hydra.git
				synced 2025-03-09 15:40:26 +00:00 
			
		
		
		
	Merge pull request #1444 from hydralauncher/fix/fixing-seeding-on-level
fix: fixing seeding on level
This commit is contained in:
		
						commit
						42ae8e76a6
					
				
					 18 changed files with 108 additions and 70 deletions
				
			
		| 
						 | 
				
			
			@ -159,6 +159,8 @@ def action():
 | 
			
		|||
        downloader = downloads.get(game_id)
 | 
			
		||||
        if downloader:
 | 
			
		||||
            downloader.pause_download()
 | 
			
		||||
        
 | 
			
		||||
        if downloading_game_id == game_id:
 | 
			
		||||
            downloading_game_id = -1
 | 
			
		||||
    elif action == 'cancel':
 | 
			
		||||
        downloader = downloads.get(game_id)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -107,7 +107,10 @@ const copyAria2Macos = async () => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const copyAria2 = () => {
 | 
			
		||||
  if (fs.existsSync("aria2")) {
 | 
			
		||||
  const aria2Path =
 | 
			
		||||
    process.platform === "win32" ? "aria2/aria2c.exe" : "aria2/aria2c";
 | 
			
		||||
 | 
			
		||||
  if (fs.existsSync(aria2Path)) {
 | 
			
		||||
    console.log("Aria2 already exists, skipping download...");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import type { Game, GameShop } from "@types";
 | 
			
		|||
import { steamGamesWorker } from "@main/workers";
 | 
			
		||||
import { createGame } from "@main/services/library-sync";
 | 
			
		||||
import { steamUrlBuilder } from "@shared";
 | 
			
		||||
import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements";
 | 
			
		||||
import { updateLocalUnlockedAchievements } from "@main/services/achievements/update-local-unlocked-achivements";
 | 
			
		||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
 | 
			
		||||
 | 
			
		||||
const addGameToLibrary = async (
 | 
			
		||||
| 
						 | 
				
			
			@ -46,9 +46,9 @@ const addGameToLibrary = async (
 | 
			
		|||
 | 
			
		||||
    await gamesSublevel.put(levelKeys.game(shop, objectId), game);
 | 
			
		||||
 | 
			
		||||
    updateLocalUnlockedAchivements(game);
 | 
			
		||||
    await createGame(game).catch(() => {});
 | 
			
		||||
 | 
			
		||||
    createGame(game).catch(() => {});
 | 
			
		||||
    updateLocalUnlockedAchievements(game);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,14 @@ const cancelGameDownload = async (
 | 
			
		|||
 | 
			
		||||
  await DownloadManager.cancelDownload(downloadKey);
 | 
			
		||||
 | 
			
		||||
  await downloadsSublevel.del(downloadKey);
 | 
			
		||||
  const download = await downloadsSublevel.get(downloadKey);
 | 
			
		||||
 | 
			
		||||
  if (!download) return;
 | 
			
		||||
 | 
			
		||||
  await downloadsSublevel.put(downloadKey, {
 | 
			
		||||
    ...download,
 | 
			
		||||
    status: "removed",
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
registerEvent("cancelGameDownload", cancelGameDownload);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ const pauseGameSeed = async (
 | 
			
		|||
 | 
			
		||||
  await downloadsSublevel.put(downloadKey, {
 | 
			
		||||
    ...download,
 | 
			
		||||
    status: "complete",
 | 
			
		||||
    shouldSeed: false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,12 +8,14 @@ const resumeGameSeed = async (
 | 
			
		|||
  shop: GameShop,
 | 
			
		||||
  objectId: string
 | 
			
		||||
) => {
 | 
			
		||||
  const download = await downloadsSublevel.get(levelKeys.game(shop, objectId));
 | 
			
		||||
  const downloadKey = levelKeys.game(shop, objectId);
 | 
			
		||||
  const download = await downloadsSublevel.get(downloadKey);
 | 
			
		||||
 | 
			
		||||
  if (!download) return;
 | 
			
		||||
 | 
			
		||||
  await downloadsSublevel.put(levelKeys.game(shop, objectId), {
 | 
			
		||||
  await downloadsSublevel.put(downloadKey, {
 | 
			
		||||
    ...download,
 | 
			
		||||
    status: "seeding",
 | 
			
		||||
    shouldSeed: true,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ export const getUnlockedAchievements = async (
 | 
			
		|||
 | 
			
		||||
  return achievementsData
 | 
			
		||||
    .map((achievementData) => {
 | 
			
		||||
      const unlockedAchiementData = unlockedAchievements.find(
 | 
			
		||||
      const unlockedAchievementData = unlockedAchievements.find(
 | 
			
		||||
        (localAchievement) => {
 | 
			
		||||
          return (
 | 
			
		||||
            localAchievement.name.toUpperCase() ==
 | 
			
		||||
| 
						 | 
				
			
			@ -45,11 +45,11 @@ export const getUnlockedAchievements = async (
 | 
			
		|||
        ? achievementData.icon
 | 
			
		||||
        : achievementData.icongray;
 | 
			
		||||
 | 
			
		||||
      if (unlockedAchiementData) {
 | 
			
		||||
      if (unlockedAchievementData) {
 | 
			
		||||
        return {
 | 
			
		||||
          ...achievementData,
 | 
			
		||||
          unlocked: true,
 | 
			
		||||
          unlockTime: unlockedAchiementData.unlockTime,
 | 
			
		||||
          unlockTime: unlockedAchievementData.unlockTime,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,23 +57,21 @@ export const loadState = async () => {
 | 
			
		|||
    .values()
 | 
			
		||||
    .all()
 | 
			
		||||
    .then((games) => {
 | 
			
		||||
      return sortBy(
 | 
			
		||||
        games.filter((game) => game.queued),
 | 
			
		||||
        "timestamp",
 | 
			
		||||
        "DESC"
 | 
			
		||||
      );
 | 
			
		||||
      return sortBy(games, "timestamp", "DESC");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const [nextItemOnQueue] = downloads;
 | 
			
		||||
  const [nextItemOnQueue] = downloads.filter((game) => game.queued);
 | 
			
		||||
 | 
			
		||||
  const downloadsToSeed = downloads.filter(
 | 
			
		||||
    (download) =>
 | 
			
		||||
      download.shouldSeed &&
 | 
			
		||||
      download.downloader === Downloader.Torrent &&
 | 
			
		||||
      download.progress === 1 &&
 | 
			
		||||
      download.uri !== null
 | 
			
		||||
    (game) =>
 | 
			
		||||
      game.shouldSeed &&
 | 
			
		||||
      game.downloader === Downloader.Torrent &&
 | 
			
		||||
      game.progress === 1 &&
 | 
			
		||||
      game.uri !== null
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  console.log("downloadsToSeed", downloadsToSeed);
 | 
			
		||||
 | 
			
		||||
  await DownloadManager.startRPC(nextItemOnQueue, downloadsToSeed);
 | 
			
		||||
 | 
			
		||||
  startMainLoop();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,23 +23,21 @@ const saveAchievementsOnLocal = async (
 | 
			
		|||
  return gameAchievementsSublevel
 | 
			
		||||
    .get(levelKey)
 | 
			
		||||
    .then(async (gameAchievement) => {
 | 
			
		||||
      if (gameAchievement) {
 | 
			
		||||
        await gameAchievementsSublevel.put(levelKey, {
 | 
			
		||||
          ...gameAchievement,
 | 
			
		||||
          unlockedAchievements: unlockedAchievements,
 | 
			
		||||
        });
 | 
			
		||||
      await gameAchievementsSublevel.put(levelKey, {
 | 
			
		||||
        achievements: gameAchievement?.achievements ?? [],
 | 
			
		||||
        unlockedAchievements: unlockedAchievements,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
        if (!sendUpdateEvent) return;
 | 
			
		||||
      if (!sendUpdateEvent) return;
 | 
			
		||||
 | 
			
		||||
        return getUnlockedAchievements(objectId, shop, true)
 | 
			
		||||
          .then((achievements) => {
 | 
			
		||||
            WindowManager.mainWindow?.webContents.send(
 | 
			
		||||
              `on-update-achievements-${objectId}-${shop}`,
 | 
			
		||||
              achievements
 | 
			
		||||
            );
 | 
			
		||||
          })
 | 
			
		||||
          .catch(() => {});
 | 
			
		||||
      }
 | 
			
		||||
      return getUnlockedAchievements(objectId, shop, true)
 | 
			
		||||
        .then((achievements) => {
 | 
			
		||||
          WindowManager.mainWindow?.webContents.send(
 | 
			
		||||
            `on-update-achievements-${objectId}-${shop}`,
 | 
			
		||||
            achievements
 | 
			
		||||
          );
 | 
			
		||||
        })
 | 
			
		||||
        .catch(() => {});
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -133,7 +131,7 @@ export const mergeAchievements = async (
 | 
			
		|||
        );
 | 
			
		||||
      })
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        if (err! instanceof SubscriptionRequiredError) {
 | 
			
		||||
        if (err instanceof SubscriptionRequiredError) {
 | 
			
		||||
          achievementsLogger.log(
 | 
			
		||||
            "Achievements not synchronized on API due to lack of subscription",
 | 
			
		||||
            game.objectId,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ import { parseAchievementFile } from "./parse-achievement-file";
 | 
			
		|||
import { mergeAchievements } from "./merge-achievements";
 | 
			
		||||
import type { Game, UnlockedAchievement } from "@types";
 | 
			
		||||
 | 
			
		||||
export const updateLocalUnlockedAchivements = async (game: Game) => {
 | 
			
		||||
export const updateLocalUnlockedAchievements = async (game: Game) => {
 | 
			
		||||
  const gameAchievementFiles = findAchievementFiles(game);
 | 
			
		||||
 | 
			
		||||
  const achievementFileInsideDirectory =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -219,8 +219,10 @@ export class DownloadManager {
 | 
			
		|||
      } as PauseDownloadPayload)
 | 
			
		||||
      .catch(() => {});
 | 
			
		||||
 | 
			
		||||
    WindowManager.mainWindow?.setProgressBar(-1);
 | 
			
		||||
    this.downloadingGameId = null;
 | 
			
		||||
    if (downloadKey === this.downloadingGameId) {
 | 
			
		||||
      WindowManager.mainWindow?.setProgressBar(-1);
 | 
			
		||||
      this.downloadingGameId = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async resumeDownload(download: Download) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ export const mergeWithRemoteGames = async () => {
 | 
			
		|||
              ? game.playTimeInMilliseconds
 | 
			
		||||
              : localGame.playTimeInMilliseconds;
 | 
			
		||||
 | 
			
		||||
          gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
 | 
			
		||||
          await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
 | 
			
		||||
            ...localGame,
 | 
			
		||||
            remoteId: game.id,
 | 
			
		||||
            lastTimePlayed: updatedLastTimePlayed,
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ export const mergeWithRemoteGames = async () => {
 | 
			
		|||
            ? steamUrlBuilder.icon(game.objectId, steamGame.clientIcon)
 | 
			
		||||
            : null;
 | 
			
		||||
 | 
			
		||||
          gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
 | 
			
		||||
          await gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
 | 
			
		||||
            objectId: game.objectId,
 | 
			
		||||
            title: steamGame?.name,
 | 
			
		||||
            remoteId: game.id,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@
 | 
			
		|||
  &__content {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    padding: calc(globals.$spacing-unit * 2);
 | 
			
		||||
    gap: calc(globals.$spacing-unit * 2);
 | 
			
		||||
    width: 100%;
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +55,7 @@
 | 
			
		|||
    display: flex;
 | 
			
		||||
    color: globals.$muted-color;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color: rgba(255, 255, 255, 0.15);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -104,13 +106,14 @@
 | 
			
		|||
  &__container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__section {
 | 
			
		||||
    gap: calc(globals.$spacing-unit * 2);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: calc(globals.$spacing-unit * 2);
 | 
			
		||||
    padding-bottom: globals.$spacing-unit;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ export function useDownload() {
 | 
			
		|||
  const pauseDownload = async (shop: GameShop, objectId: string) => {
 | 
			
		||||
    await window.electron.pauseGameDownload(shop, objectId);
 | 
			
		||||
    await updateLibrary();
 | 
			
		||||
    dispatch(clearDownload());
 | 
			
		||||
    if (lastPacket?.gameId === `${shop}:${objectId}`) dispatch(clearDownload());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const resumeDownload = async (shop: GameShop, objectId: string) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,14 @@
 | 
			
		|||
  flex-direction: column;
 | 
			
		||||
  gap: calc(globals.$spacing-unit * 2);
 | 
			
		||||
 | 
			
		||||
  &__details-with-article {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: calc(globals.$spacing-unit / 2);
 | 
			
		||||
    align-self: flex-start;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__header {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ import {
 | 
			
		|||
  DownloadIcon,
 | 
			
		||||
  LinkIcon,
 | 
			
		||||
  PlayIcon,
 | 
			
		||||
  QuestionIcon,
 | 
			
		||||
  ThreeBarsIcon,
 | 
			
		||||
  TrashIcon,
 | 
			
		||||
  UnlinkIcon,
 | 
			
		||||
| 
						 | 
				
			
			@ -122,8 +123,12 @@ export function DownloadGroup({
 | 
			
		|||
          </p>
 | 
			
		||||
 | 
			
		||||
          {download.downloader === Downloader.Torrent && (
 | 
			
		||||
            <small>
 | 
			
		||||
            <small
 | 
			
		||||
              className="download-group__details-with-article"
 | 
			
		||||
              data-open-article="peers-and-seeds"
 | 
			
		||||
            >
 | 
			
		||||
              {lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds
 | 
			
		||||
              <QuestionIcon size={12} />
 | 
			
		||||
            </small>
 | 
			
		||||
          )}
 | 
			
		||||
        </>
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +141,14 @@ export function DownloadGroup({
 | 
			
		|||
      return download.status === "seeding" &&
 | 
			
		||||
        download.downloader === Downloader.Torrent ? (
 | 
			
		||||
        <>
 | 
			
		||||
          <p>{t("seeding")}</p>
 | 
			
		||||
          <p
 | 
			
		||||
            data-open-article="seeding"
 | 
			
		||||
            className="download-group__details-with-article"
 | 
			
		||||
          >
 | 
			
		||||
            {t("seeding")}
 | 
			
		||||
 | 
			
		||||
            <QuestionIcon />
 | 
			
		||||
          </p>
 | 
			
		||||
          {uploadSpeed && <p>{uploadSpeed}/s</p>}
 | 
			
		||||
        </>
 | 
			
		||||
      ) : (
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +186,7 @@ export function DownloadGroup({
 | 
			
		|||
 | 
			
		||||
    const deleting = isGameDeleting(game.id);
 | 
			
		||||
 | 
			
		||||
    if (download?.progress === 1) {
 | 
			
		||||
    if (game.download?.progress === 1) {
 | 
			
		||||
      return [
 | 
			
		||||
        {
 | 
			
		||||
          label: t("install"),
 | 
			
		||||
| 
						 | 
				
			
			@ -189,8 +201,8 @@ export function DownloadGroup({
 | 
			
		|||
          disabled: deleting,
 | 
			
		||||
          icon: <UnlinkIcon />,
 | 
			
		||||
          show:
 | 
			
		||||
            download.status === "seeding" &&
 | 
			
		||||
            download.downloader === Downloader.Torrent,
 | 
			
		||||
            game.download?.status === "seeding" &&
 | 
			
		||||
            game.download?.downloader === Downloader.Torrent,
 | 
			
		||||
          onClick: () => {
 | 
			
		||||
            pauseSeeding(game.shop, game.objectId);
 | 
			
		||||
          },
 | 
			
		||||
| 
						 | 
				
			
			@ -200,8 +212,8 @@ export function DownloadGroup({
 | 
			
		|||
          disabled: deleting,
 | 
			
		||||
          icon: <LinkIcon />,
 | 
			
		||||
          show:
 | 
			
		||||
            download.status !== "seeding" &&
 | 
			
		||||
            download.downloader === Downloader.Torrent,
 | 
			
		||||
            game.download?.status !== "seeding" &&
 | 
			
		||||
            game.download?.downloader === Downloader.Torrent,
 | 
			
		||||
          onClick: () => {
 | 
			
		||||
            resumeSeeding(game.shop, game.objectId);
 | 
			
		||||
          },
 | 
			
		||||
| 
						 | 
				
			
			@ -217,7 +229,7 @@ export function DownloadGroup({
 | 
			
		|||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isGameDownloading || download?.status === "active") {
 | 
			
		||||
    if (isGameDownloading) {
 | 
			
		||||
      return [
 | 
			
		||||
        {
 | 
			
		||||
          label: t("pause"),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ import "./downloads.scss";
 | 
			
		|||
import { DeleteGameModal } from "./delete-game-modal";
 | 
			
		||||
import { DownloadGroup } from "./download-group";
 | 
			
		||||
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
 | 
			
		||||
import { orderBy, sortBy } from "lodash-es";
 | 
			
		||||
import { orderBy } from "lodash-es";
 | 
			
		||||
import { ArrowDownIcon } from "@primer/octicons-react";
 | 
			
		||||
 | 
			
		||||
export default function Downloads() {
 | 
			
		||||
| 
						 | 
				
			
			@ -58,24 +58,24 @@ export default function Downloads() {
 | 
			
		|||
      complete: [],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const result = sortBy(library, (game) => game.download?.timestamp).reduce(
 | 
			
		||||
      (prev, next) => {
 | 
			
		||||
        /* Game has been manually added to the library or has been canceled */
 | 
			
		||||
        if (!next.download?.status || next.download?.status === "removed")
 | 
			
		||||
          return prev;
 | 
			
		||||
    const result = orderBy(
 | 
			
		||||
      library,
 | 
			
		||||
      (game) => game.download?.timestamp,
 | 
			
		||||
      "desc"
 | 
			
		||||
    ).reduce((prev, next) => {
 | 
			
		||||
      /* Game has been manually added to the library */
 | 
			
		||||
      if (!next.download) return prev;
 | 
			
		||||
 | 
			
		||||
        /* Is downloading */
 | 
			
		||||
        if (lastPacket?.gameId === next.id)
 | 
			
		||||
          return { ...prev, downloading: [...prev.downloading, next] };
 | 
			
		||||
      /* Is downloading */
 | 
			
		||||
      if (lastPacket?.gameId === next.id)
 | 
			
		||||
        return { ...prev, downloading: [...prev.downloading, next] };
 | 
			
		||||
 | 
			
		||||
        /* Is either queued or paused */
 | 
			
		||||
        if (next.download.queued || next.download?.status === "paused")
 | 
			
		||||
          return { ...prev, queued: [...prev.queued, next] };
 | 
			
		||||
      /* Is either queued or paused */
 | 
			
		||||
      if (next.download.queued || next.download?.status === "paused")
 | 
			
		||||
        return { ...prev, queued: [...prev.queued, next] };
 | 
			
		||||
 | 
			
		||||
        return { ...prev, complete: [...prev.complete, next] };
 | 
			
		||||
      },
 | 
			
		||||
      initialValue
 | 
			
		||||
    );
 | 
			
		||||
      return { ...prev, complete: [...prev.complete, next] };
 | 
			
		||||
    }, initialValue);
 | 
			
		||||
 | 
			
		||||
    const queued = orderBy(result.queued, (game) => game.download?.timestamp, [
 | 
			
		||||
      "desc",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -163,8 +163,10 @@ export function DownloadSettingsModal({
 | 
			
		|||
                  selectedDownloader === downloader ? "primary" : "outline"
 | 
			
		||||
                }
 | 
			
		||||
                disabled={
 | 
			
		||||
                  downloader === Downloader.RealDebrid &&
 | 
			
		||||
                  !userPreferences?.realDebridApiToken
 | 
			
		||||
                  (downloader === Downloader.RealDebrid &&
 | 
			
		||||
                    !userPreferences?.realDebridApiToken) ||
 | 
			
		||||
                  (downloader === Downloader.TorBox &&
 | 
			
		||||
                    !userPreferences?.torBoxApiToken)
 | 
			
		||||
                }
 | 
			
		||||
                onClick={() => setSelectedDownloader(downloader)}
 | 
			
		||||
              >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue