-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: leverage slippi backend's getLatestDolphin query #271
Changes from all commits
5b11ee9
618ede7
f6e0000
bc636d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { ApolloClient, gql, HttpLink, InMemoryCache } from "@apollo/client"; | ||
import { isDevelopment } from "common/constants"; | ||
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}${isDevelopment ? "-dev" : ""}`, | ||
}); | ||
|
||
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<DolphinVersionResponse> { | ||
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, | ||
}, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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,26 @@ 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<void> { | ||
try { | ||
await assertDolphinInstallation(DolphinLaunchType.NETPLAY, logDownloadInfo); | ||
await assertDolphinInstallation(DolphinLaunchType.PLAYBACK, logDownloadInfo); | ||
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally we could use Promise.all so we could potentially avoid a waterfall. However, since we're sending the log to |
||
await ipc_dolphinDownloadFinishedEvent.main!.trigger({ error: null }); | ||
} catch (err) { | ||
console.error(err); | ||
await ipc_dolphinDownloadFinishedEvent.main!.trigger({ error: err.message }); | ||
} | ||
} | ||
|
||
export async function assertDolphinInstallation( | ||
type: DolphinLaunchType, | ||
log: (message: string) => void, | ||
|
@@ -27,106 +38,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 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, log); | ||
await downloadAndInstallDolphin(type, dolphinDownloadInfo, log); | ||
return; | ||
} | ||
log("No update found..."); | ||
return; | ||
} catch (err) { | ||
log(`Could not find ${type} Dolphin installation. Downloading...`); | ||
await downloadAndInstallDolphin(type, log); | ||
const dolphinDownloadInfo = await fetchLatestDolphin(type); | ||
await downloadAndInstallDolphin(type, dolphinDownloadInfo, log); | ||
} | ||
} | ||
|
||
export async function downloadAndInstallDolphin( | ||
type: DolphinLaunchType, | ||
log: (message: string) => void, | ||
cleanInstall = false, | ||
): Promise<void> { | ||
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<void> { | ||
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<boolean> { | ||
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<ChildProcessWithoutNullStreams> { | ||
const dolphinPath = await findDolphinExecutable(type); | ||
return spawn(dolphinPath, params); | ||
} | ||
|
||
async function getLatestDolphinAsset(type: DolphinLaunchType): Promise<any> { | ||
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<any> { | ||
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<void> { | ||
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( | ||
type: DolphinLaunchType, | ||
downloadUrl: string, | ||
log: (status: string) => void = console.log, | ||
): Promise<string> { | ||
const asset = await getLatestDolphinAsset(type); | ||
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, | ||
Comment on lines
+92
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a comment, but I think it would be good if we could eventually not depend on the existence of a browser window to download a file since |
||
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"); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to provide a link here instead of just providing the
uri
directly? Does it not work if we don't provide it an instance tocross-fetch
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, we get this explicit error from ApolloClient