From 5b11ee9b6bf3b6ef6944d4e1f2869a3bf8892b7f Mon Sep 17 00:00:00 2001 From: Nikhil Narayana Date: Fri, 28 Jan 2022 11:36:18 -0800 Subject: [PATCH 1/4] feat: add query for dolphin version and downloads --- src/dolphin/checkVersion.ts | 62 +++++++++++++++++++++++++++++++++++++ src/dolphin/manager.ts | 4 ++- src/dolphin/types.ts | 9 ++++++ webpack.main.additions.js | 8 +++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/dolphin/checkVersion.ts diff --git a/src/dolphin/checkVersion.ts b/src/dolphin/checkVersion.ts new file mode 100644 index 000000000..742d600f8 --- /dev/null +++ b/src/dolphin/checkVersion.ts @@ -0,0 +1,62 @@ +import { ApolloClient, gql, HttpLink, InMemoryCache } from "@apollo/client"; +import { fetch } from "cross-fetch"; +import { GraphQLError } from "graphql"; + +import { DolphinLaunchType, DolphinVersionResponse } from "./types"; + +const httpLink = new HttpLink({ uri: process.env.SLIPPI_GRAPHQL_ENDPOINT, fetch }); + +const appVersion = __VERSION__; + +const client = new ApolloClient({ + link: httpLink, + cache: new InMemoryCache(), + name: "slippi-launcher", + version: appVersion, +}); + +const getLatestDolphinQuery = gql` + query GetLatestDolphin($purpose: DolphinPurpose, $includeBeta: Boolean) { + getLatestDolphin(purpose: $purpose, includeBeta: $includeBeta) { + linuxDownloadUrl + windowsDownloadUrl + macDownloadUrl + version + } + } +`; + +const handleErrors = (errors: readonly GraphQLError[] | undefined) => { + if (errors) { + let errMsgs = ""; + errors.forEach((err) => { + errMsgs += `${err.message}\n`; + }); + throw new Error(errMsgs); + } +}; + +export async function fetchLatestDolphin( + dolphinType: DolphinLaunchType, + beta = false, +): Promise { + const res = await client.query({ + query: getLatestDolphinQuery, + fetchPolicy: "network-only", + variables: { + purpose: dolphinType.toUpperCase(), + includeBeta: beta, + }, + }); + + handleErrors(res.errors); + + return { + version: res.data.getLatestDolphin.version, + downloadUrls: { + darwin: res.data.getLatestDolphin.macDownloadUrl, + linux: res.data.getLatestDolphin.linuxDownloadUrl, + win32: res.data.getLatestDolphin.windowsDownloadUrl, + }, + }; +} diff --git a/src/dolphin/manager.ts b/src/dolphin/manager.ts index 8104264a7..eb6335f16 100644 --- a/src/dolphin/manager.ts +++ b/src/dolphin/manager.ts @@ -6,6 +6,7 @@ import * as fs from "fs-extra"; import { fileExists } from "main/fileExists"; import path from "path"; +import { fetchLatestDolphin } from "./checkVersion"; import { downloadAndInstallDolphin } from "./downloadDolphin"; import { DolphinInstance, PlaybackDolphinInstance } from "./instance"; import { DolphinLaunchType, ReplayCommunication } from "./types"; @@ -126,7 +127,8 @@ export class DolphinManager extends EventEmitter { } // No dolphins of launchType are open so lets reinstall - await downloadAndInstallDolphin(launchType, log.info, true); + const releaseInfo = await fetchLatestDolphin(launchType); + await downloadAndInstallDolphin(launchType, releaseInfo, log.info, true); const isoPath = settingsManager.get().settings.isoPath; if (isoPath) { const gameDir = path.dirname(isoPath); diff --git a/src/dolphin/types.ts b/src/dolphin/types.ts index 3766beaa9..c3335080c 100644 --- a/src/dolphin/types.ts +++ b/src/dolphin/types.ts @@ -38,3 +38,12 @@ export interface PlayKey { displayName: string; latestVersion: string; } + +export interface DolphinVersionResponse { + version: string; + downloadUrls: { + darwin: string; + linux: string; + win32: string; + }; +} diff --git a/webpack.main.additions.js b/webpack.main.additions.js index 137bd762f..6f847a804 100644 --- a/webpack.main.additions.js +++ b/webpack.main.additions.js @@ -1,6 +1,8 @@ const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); const ThreadsPlugin = require("threads-plugin"); const Dotenv = require("dotenv-webpack"); +const { DefinePlugin } = require("webpack"); +const pkg = require("./package.json"); module.exports = function (context) { // Enforce chunkhash when building output files. @@ -29,5 +31,11 @@ module.exports = function (context) { new Dotenv(), ); + context.plugins.push( + new DefinePlugin({ + __VERSION__: JSON.stringify(pkg.version), + }), + ); + return context; }; From 618ede7da0726c36580e746648b240ab10ac6bb9 Mon Sep 17 00:00:00 2001 From: Nikhil Narayana Date: Fri, 28 Jan 2022 11:36:45 -0800 Subject: [PATCH 2/4] refactor: use getLatestDolphin query --- src/dolphin/downloadDolphin.ts | 111 ++++++++++++--------------------- 1 file changed, 40 insertions(+), 71 deletions(-) diff --git a/src/dolphin/downloadDolphin.ts b/src/dolphin/downloadDolphin.ts index 62e89b1c8..1d5b7d65d 100644 --- a/src/dolphin/downloadDolphin.ts +++ b/src/dolphin/downloadDolphin.ts @@ -1,5 +1,5 @@ import AdmZip from "adm-zip"; -import { ChildProcessWithoutNullStreams, spawn, spawnSync } from "child_process"; +import { spawnSync } from "child_process"; import { isLinux } from "common/constants"; import { app, BrowserWindow } from "electron"; import { download } from "electron-dl"; @@ -10,15 +10,27 @@ import path from "path"; import { lt } from "semver"; import { fileExists } from "../main/fileExists"; -import { getLatestRelease } from "../main/github"; +import { fetchLatestDolphin } from "./checkVersion"; import { ipc_dolphinDownloadFinishedEvent, ipc_dolphinDownloadLogReceivedEvent } from "./ipc"; -import { DolphinLaunchType } from "./types"; +import { DolphinLaunchType, DolphinVersionResponse } from "./types"; import { findDolphinExecutable } from "./util"; function logDownloadInfo(message: string): void { void ipc_dolphinDownloadLogReceivedEvent.main!.trigger({ message }); } +export async function assertDolphinInstallations(): Promise { + try { + await assertDolphinInstallation(DolphinLaunchType.NETPLAY, logDownloadInfo); + await assertDolphinInstallation(DolphinLaunchType.PLAYBACK, logDownloadInfo); + await ipc_dolphinDownloadFinishedEvent.main!.trigger({ error: null }); + } catch (err) { + console.error(err); + await ipc_dolphinDownloadFinishedEvent.main!.trigger({ error: err.message }); + } + return; +} + export async function assertDolphinInstallation( type: DolphinLaunchType, log: (message: string) => void, @@ -27,106 +39,63 @@ export async function assertDolphinInstallation( await findDolphinExecutable(type); log(`Found existing ${type} Dolphin executable.`); log(`Checking if we need to update ${type} Dolphin`); - const data = await getLatestReleaseData(type); - const latestVersion = data.tag_name; + const data = await fetchLatestDolphin(type); + const latestVersion = data.version; const isOutdated = await compareDolphinVersion(type, latestVersion); if (isOutdated) { log(`${type} Dolphin installation is outdated. Downloading latest...`); - await downloadAndInstallDolphin(type, log); + await downloadAndInstallDolphin(type, data, log); return; } log("No update found..."); return; } catch (err) { log(`Could not find ${type} Dolphin installation. Downloading...`); - await downloadAndInstallDolphin(type, log); + const data = await fetchLatestDolphin(type); + await downloadAndInstallDolphin(type, data, log); } } -export async function downloadAndInstallDolphin( - type: DolphinLaunchType, - log: (message: string) => void, - cleanInstall = false, -): Promise { - const downloadedAsset = await downloadLatestDolphin(type, log); - log(`Installing ${type} Dolphin...`); - await installDolphin(type, downloadedAsset, log, cleanInstall); - log(`Finished ${type} installing`); -} - -export async function assertDolphinInstallations(): Promise { - try { - await assertDolphinInstallation(DolphinLaunchType.NETPLAY, logDownloadInfo); - await assertDolphinInstallation(DolphinLaunchType.PLAYBACK, logDownloadInfo); - await ipc_dolphinDownloadFinishedEvent.main!.trigger({ error: null }); - } catch (err) { - console.error(err); - await ipc_dolphinDownloadFinishedEvent.main!.trigger({ error: err.message }); - } - return; -} - async function compareDolphinVersion(type: DolphinLaunchType, latestVersion: string): Promise { const dolphinPath = await findDolphinExecutable(type); const dolphinVersion = spawnSync(dolphinPath, ["--version"]).stdout.toString(); return lt(dolphinVersion, latestVersion); } -export async function openDolphin(type: DolphinLaunchType, params?: string[]): Promise { - const dolphinPath = await findDolphinExecutable(type); - return spawn(dolphinPath, params); -} - -async function getLatestDolphinAsset(type: DolphinLaunchType): Promise { - const release = await getLatestReleaseData(type); - const asset = release.assets.find((a: any) => matchesPlatform(a.name)); - if (!asset) { - throw new Error(`No release asset matched the current platform: ${process.platform}`); - } - return asset; -} - -async function getLatestReleaseData(type: DolphinLaunchType): Promise { - const owner = "project-slippi"; - let repo = "Ishiiruka"; - if (type === DolphinLaunchType.PLAYBACK) { - repo += "-Playback"; - } - return getLatestRelease(owner, repo); -} - -function matchesPlatform(releaseName: string): boolean { - switch (process.platform) { - case "win32": - return releaseName.endsWith("Win.zip"); - case "darwin": - return releaseName.endsWith("Mac.dmg"); - case "linux": - return releaseName.endsWith("Linux.zip"); - default: - return false; - } +export async function downloadAndInstallDolphin( + type: DolphinLaunchType, + releaseInfo: DolphinVersionResponse, + log: (message: string) => void, + cleanInstall = false, +): Promise { + const downloadedAsset = await downloadLatestDolphin(releaseInfo, log); + log(`Installing v${releaseInfo.version} ${type} Dolphin...`); + await installDolphin(type, downloadedAsset, log, cleanInstall); + log(`Finished v${releaseInfo.version} ${type} Dolphin install`); } async function downloadLatestDolphin( - type: DolphinLaunchType, + releaseInfo: DolphinVersionResponse, log: (status: string) => void = console.log, ): Promise { - const asset = await getLatestDolphinAsset(type); + const downloadUrl = releaseInfo.downloadUrls[process.platform]; + const parsedUrl = new URL(downloadUrl); + const filename = path.basename(parsedUrl.pathname); + const downloadDir = path.join(app.getPath("userData"), "temp"); await fs.ensureDir(downloadDir); - const downloadLocation = path.join(downloadDir, asset.name); + const downloadLocation = path.join(downloadDir, filename); const exists = await fileExists(downloadLocation); if (!exists) { - log(`Downloading ${asset.browser_download_url} to ${downloadLocation}`); + log(`Downloading ${downloadUrl} to ${downloadLocation}`); const win = BrowserWindow.getFocusedWindow(); if (win) { - await download(win, asset.browser_download_url, { - filename: asset.name, + await download(win, downloadUrl, { + filename: filename, directory: downloadDir, onProgress: (progress) => log(`Downloading... ${(progress.percent * 100).toFixed(0)}%`), }); - log(`Successfully downloaded ${asset.browser_download_url} to ${downloadLocation}`); + log(`Successfully downloaded ${downloadUrl} to ${downloadLocation}`); } else { log("I dunno how we got here, but apparently there isn't a browser window /shrug"); } From f6e0000adffada3a44b4909fc0143cf688cd7200 Mon Sep 17 00:00:00 2001 From: Nikhil Narayana Date: Fri, 28 Jan 2022 12:52:23 -0800 Subject: [PATCH 3/4] feat: append dev flag to apollo client version --- src/dolphin/checkVersion.ts | 3 ++- src/renderer/lib/slippiBackend.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dolphin/checkVersion.ts b/src/dolphin/checkVersion.ts index 742d600f8..ec17d65da 100644 --- a/src/dolphin/checkVersion.ts +++ b/src/dolphin/checkVersion.ts @@ -1,4 +1,5 @@ import { ApolloClient, gql, HttpLink, InMemoryCache } from "@apollo/client"; +import { isDevelopment } from "common/constants"; import { fetch } from "cross-fetch"; import { GraphQLError } from "graphql"; @@ -12,7 +13,7 @@ const client = new ApolloClient({ link: httpLink, cache: new InMemoryCache(), name: "slippi-launcher", - version: appVersion, + version: `${appVersion}${isDevelopment ? "-dev" : ""}`, }); const getLatestDolphinQuery = gql` diff --git a/src/renderer/lib/slippiBackend.ts b/src/renderer/lib/slippiBackend.ts index 296c9659a..9c3c2e245 100644 --- a/src/renderer/lib/slippiBackend.ts +++ b/src/renderer/lib/slippiBackend.ts @@ -1,6 +1,7 @@ import { ApolloClient, ApolloLink, gql, HttpLink, InMemoryCache } from "@apollo/client"; import { ipc_checkPlayKeyExists, ipc_removePlayKeyFile, ipc_storePlayKeyFile } from "@dolphin/ipc"; import { PlayKey } from "@dolphin/types"; +import { isDevelopment } from "common/constants"; import electronLog from "electron-log"; import firebase from "firebase"; import { GraphQLError } from "graphql"; @@ -15,7 +16,7 @@ const client = new ApolloClient({ link: httpLink, cache: new InMemoryCache(), name: "slippi-launcher", - version: appVersion, + version: `${appVersion}${isDevelopment ? "-dev" : ""}`, }); const getUserKeyQuery = gql` From bc636d40d3ace9cb68a31ae7de4aa1457cd88b12 Mon Sep 17 00:00:00 2001 From: Nikhil Narayana Date: Sun, 30 Jan 2022 19:18:24 -0800 Subject: [PATCH 4/4] fix: resolve PR comments --- src/dolphin/downloadDolphin.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/dolphin/downloadDolphin.ts b/src/dolphin/downloadDolphin.ts index 1d5b7d65d..ad16ae95a 100644 --- a/src/dolphin/downloadDolphin.ts +++ b/src/dolphin/downloadDolphin.ts @@ -28,7 +28,6 @@ export async function assertDolphinInstallations(): Promise { console.error(err); await ipc_dolphinDownloadFinishedEvent.main!.trigger({ error: err.message }); } - return; } export async function assertDolphinInstallation( @@ -39,20 +38,20 @@ export async function assertDolphinInstallation( await findDolphinExecutable(type); log(`Found existing ${type} Dolphin executable.`); log(`Checking if we need to update ${type} Dolphin`); - const data = await fetchLatestDolphin(type); - const latestVersion = data.version; + const dolphinDownloadInfo = await fetchLatestDolphin(type); + const latestVersion = dolphinDownloadInfo.version; const isOutdated = await compareDolphinVersion(type, latestVersion); if (isOutdated) { log(`${type} Dolphin installation is outdated. Downloading latest...`); - await downloadAndInstallDolphin(type, data, log); + await downloadAndInstallDolphin(type, dolphinDownloadInfo, log); return; } log("No update found..."); return; } catch (err) { log(`Could not find ${type} Dolphin installation. Downloading...`); - const data = await fetchLatestDolphin(type); - await downloadAndInstallDolphin(type, data, log); + const dolphinDownloadInfo = await fetchLatestDolphin(type); + await downloadAndInstallDolphin(type, dolphinDownloadInfo, log); } } @@ -68,17 +67,17 @@ export async function downloadAndInstallDolphin( log: (message: string) => void, cleanInstall = false, ): Promise { - const downloadedAsset = await downloadLatestDolphin(releaseInfo, log); + const downloadUrl = releaseInfo.downloadUrls[process.platform]; + const downloadedAsset = await downloadLatestDolphin(downloadUrl, log); log(`Installing v${releaseInfo.version} ${type} Dolphin...`); await installDolphin(type, downloadedAsset, log, cleanInstall); log(`Finished v${releaseInfo.version} ${type} Dolphin install`); } async function downloadLatestDolphin( - releaseInfo: DolphinVersionResponse, + downloadUrl: string, log: (status: string) => void = console.log, ): Promise { - const downloadUrl = releaseInfo.downloadUrls[process.platform]; const parsedUrl = new URL(downloadUrl); const filename = path.basename(parsedUrl.pathname);