-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Basic byond fetching and extracting in the byond panel
- Loading branch information
1 parent
3e40c0e
commit 136792e
Showing
8 changed files
with
217 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,48 @@ | ||
@if (byondService.latestVersion | async; as latestVersions) { | ||
<p> | ||
Latest stable: <span class="font-bold">{{ latestVersions.stable }}</span> | ||
<button | ||
tuiButton | ||
size="xs" | ||
(click)="byondService.getVersion(latestVersions.stable)" | ||
> | ||
Fetch | ||
</button> | ||
</p> | ||
@if (latestVersions.beta) { | ||
<p> | ||
Latest beta: <span class="font-bold">{{ latestVersions.beta }}</span> | ||
<button | ||
tuiButton | ||
size="xs" | ||
(click)="byondService.getVersion(latestVersions.beta)" | ||
> | ||
Fetch | ||
</button> | ||
</p> | ||
} | ||
} @else { | ||
Loading latest version... | ||
} | ||
|
||
<ul> | ||
@for (version of byondService.versions; track version[0]) { | ||
{{ version[0] }} ({{ statusToMessage[version[1]] }}) | ||
<button | ||
tuiButton | ||
appearance="primary" | ||
size="xs" | ||
(click)="byondService.setActive(version[0])" | ||
> | ||
Set active | ||
</button> | ||
<button | ||
tuiButton | ||
appearance="secondary-destructive" | ||
size="xs" | ||
(click)="byondService.deleteVersion(version[0])" | ||
> | ||
Delete | ||
</button> | ||
} | ||
</ul> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
export class SharedLock { | ||
private chain = Promise.resolve(); | ||
|
||
public wrap<T extends (...args: any[]) => Promise<any>>( | ||
fn: T, | ||
): (...args: [...Parameters<T>, skipLock?: boolean]) => ReturnType<T> { | ||
return (...args) => { | ||
let skipLock = false; | ||
let params: Parameters<T>; | ||
|
||
if (args.length !== fn.length) skipLock = args.pop() ?? false; | ||
params = args as any; | ||
if (skipLock) return fn(...params) as ReturnType<T>; | ||
return this.run(fn, ...params); | ||
}; | ||
} | ||
|
||
public run<T extends (...args: any[]) => Promise<any>>( | ||
fn: T, | ||
...args: Parameters<T> | ||
): ReturnType<T> { | ||
return (this.chain = this.chain.finally(() => | ||
fn(...args), | ||
)) as ReturnType<T>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,133 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { HttpClient } from '@angular/common/http'; | ||
import { firstValueFrom, map } from 'rxjs'; | ||
import { firstValueFrom } from 'rxjs'; | ||
import { SharedLock } from '../utils/sharedLock'; | ||
import { CommandQueueService } from './commandQueue.service'; | ||
import { EmulatorService } from './emulator.service'; | ||
|
||
export enum VersionStatus { | ||
Fetching, | ||
Fetched, | ||
Loading, | ||
Extracting, | ||
Loaded, | ||
} | ||
|
||
@Injectable({ | ||
providedIn: 'root', | ||
}) | ||
export class ByondService { | ||
public latestVersion: Promise<{ beta?: ByondVersion; stable: ByondVersion }>; | ||
public latestVersion: Promise<{ beta?: string; stable: string }>; | ||
private lock = new SharedLock(); | ||
|
||
constructor(httpClient: HttpClient) { | ||
constructor( | ||
private httpClient: HttpClient, | ||
private commandQueueService: CommandQueueService, | ||
private emulatorService: EmulatorService, | ||
) { | ||
this.latestVersion = firstValueFrom( | ||
httpClient | ||
.get('https://secure.byond.com/download/version.txt', { | ||
responseType: 'text', | ||
}) | ||
.pipe( | ||
map((x) => { | ||
const [stable, beta] = x | ||
.split('\n') | ||
.filter((x) => x) | ||
.map((x) => new ByondVersion(x)); | ||
return { stable, beta }; | ||
}), | ||
), | ||
httpClient.get('https://secure.byond.com/download/version.txt', { | ||
responseType: 'text', | ||
}), | ||
).then((x) => { | ||
const [stable, beta] = x.split('\n').filter((x) => x); | ||
return { stable, beta }; | ||
}); | ||
void this.lock.run(() => | ||
commandQueueService.runToSuccess( | ||
'/bin/mkdir', | ||
'-p\0/mnt/host/byond\0/var/lib/byond', | ||
), | ||
); | ||
void this.lock.run(async () => { | ||
for await (const version of (await this.getByondFolder()).keys()) { | ||
this._versions.set(version, VersionStatus.Fetched); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
export class ByondVersion { | ||
public readonly major: number; | ||
public readonly minor: number; | ||
|
||
constructor(version: string); | ||
constructor(major: number, minor: number); | ||
constructor(versionOrMajor: string | number, minor?: number) { | ||
if (typeof versionOrMajor === 'number') { | ||
this.major = versionOrMajor; | ||
this.minor = minor!; | ||
} else { | ||
console.log(versionOrMajor.split('.')); | ||
const [major, minor] = versionOrMajor.split('.').map((x) => parseInt(x)); | ||
this.major = major; | ||
this.minor = minor; | ||
} | ||
private _versions = new Map<string, VersionStatus>(); | ||
|
||
public get versions(): ReadonlyMap<string, VersionStatus> { | ||
return this._versions; | ||
} | ||
|
||
toString() { | ||
return `${this.major}.${this.minor}`; | ||
public deleteVersion = this.lock.wrap(async (version: string) => { | ||
const installs = await this.getByondFolder(); | ||
await installs.removeEntry(version.toString()); | ||
this._versions.delete(version.toString()); | ||
await this.commandQueueService.runToCompletion( | ||
'/bin/rm', | ||
`-rf\0/var/lib/byond/${version}.zip\0/var/lib/byond/${version}`, | ||
); | ||
}); | ||
public getVersion = this.lock.wrap(async (version: string) => { | ||
try { | ||
const installs = await this.getByondFolder(); | ||
const handle = await installs.getFileHandle(version.toString(), { | ||
create: true, | ||
}); | ||
const readHandle = await handle.getFile(); | ||
if (readHandle.size != 0) return readHandle; | ||
|
||
this._versions.set(version.toString(), VersionStatus.Fetching); | ||
const major = version.split('.')[0]; | ||
const zipFile = await firstValueFrom( | ||
this.httpClient.get( | ||
`https://www.byond.com/download/build/${major}/${version}_byond_linux.zip`, | ||
{ responseType: 'blob' }, | ||
), | ||
); | ||
const writeHandle = await handle.createWritable(); | ||
await writeHandle.write(zipFile); | ||
this._versions.set(version.toString(), VersionStatus.Fetched); | ||
await writeHandle.close(); | ||
return new File([zipFile], version); | ||
} catch (e) { | ||
void this.deleteVersion(version); | ||
this._versions.delete(version.toString()); | ||
throw e; | ||
} | ||
}); | ||
public setActive = this.lock.wrap(async (version: string) => { | ||
const status = this._versions.get(version); | ||
if (status == null || status < VersionStatus.Fetched) return; | ||
|
||
if (status < VersionStatus.Loaded) { | ||
try { | ||
this._versions.set(version, VersionStatus.Loading); | ||
const zipFile = await this.getVersion(version, true); | ||
await this.emulatorService.sendFile( | ||
`byond/${version}.zip`, | ||
new Uint8Array(await zipFile.arrayBuffer()), | ||
); | ||
this._versions.set(version, VersionStatus.Extracting); | ||
await this.commandQueueService.runToSuccess( | ||
'/bin/mv', | ||
`/mnt/host/byond/${version}.zip\0/var/lib/byond/`, | ||
); | ||
await this.commandQueueService.runToSuccess( | ||
'/bin/unzip', | ||
`/var/lib/byond/${version}.zip\0byond/bin*\0-j\0-d\0/var/lib/byond/${version}`, | ||
); | ||
await this.commandQueueService.runToSuccess( | ||
'/bin/rm', | ||
`/var/lib/byond/${version}.zip`, | ||
); | ||
this._versions.set(version, VersionStatus.Loaded); | ||
} catch (e) { | ||
this._versions.set(version, VersionStatus.Fetched); | ||
await this.commandQueueService.runToCompletion( | ||
'/bin/rm', | ||
`-rf\0/var/lib/byond/${version}.zip\0/var/lib/byond/${version}`, | ||
); | ||
throw e; | ||
} | ||
} | ||
}); | ||
|
||
private async getByondFolder() { | ||
const opfs = await navigator.storage.getDirectory(); | ||
return await opfs.getDirectoryHandle('byond', { create: true }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters