Skip to content

Commit

Permalink
Merge pull request #43 from moonlight-mod/notnite/moonbase-unique-ids
Browse files Browse the repository at this point in the history
moonbase: Use unique IDs for extension management
  • Loading branch information
NotNite authored Apr 11, 2024
2 parents 2e0bede + 4c998a5 commit d06944e
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 117 deletions.
1 change: 1 addition & 0 deletions packages/core-extensions/src/moonbase/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export enum ExtensionState {

export type MoonbaseExtension = {
id: string;
uniqueId: number;
manifest: ExtensionManifest | RepositoryManifest;
source: DetectedExtension["source"];
state: ExtensionState;
Expand Down
174 changes: 93 additions & 81 deletions packages/core-extensions/src/moonbase/webpackModules/stores.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { Config, ExtensionLoadSource } from "@moonlight-mod/types";
import {
ExtensionState,
MoonbaseExtension,
MoonbaseNatives,
RepositoryManifest
} from "../types";
import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types";
import Flux from "@moonlight-mod/wp/common_flux";
import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher";

Expand All @@ -14,19 +9,21 @@ const logger = moonlight.getLogger("moonbase");
class MoonbaseSettingsStore extends Flux.Store<any> {
private origConfig: Config;
private config: Config;
private extensionIndex: number;

modified: boolean;
submitting: boolean;
installing: boolean;

extensions: { [id: string]: MoonbaseExtension };
updates: { [id: string]: { version: string; download: string } };
extensions: { [id: number]: MoonbaseExtension };
updates: { [id: number]: { version: string; download: string } };

constructor() {
super(Dispatcher);

this.origConfig = moonlightNode.config;
this.config = this.clone(this.origConfig);
this.extensionIndex = 0;

this.modified = false;
this.submitting = false;
Expand All @@ -35,11 +32,10 @@ class MoonbaseSettingsStore extends Flux.Store<any> {
this.extensions = {};
this.updates = {};
for (const ext of moonlightNode.extensions) {
const existingExtension = this.extensions[ext.id];
if (existingExtension != null) continue;

this.extensions[ext.id] = {
const uniqueId = this.extensionIndex++;
this.extensions[uniqueId] = {
...ext,
uniqueId,
state: moonlight.enabledExtensions.has(ext.id)
? ExtensionState.Enabled
: ExtensionState.Disabled
Expand All @@ -50,34 +46,27 @@ class MoonbaseSettingsStore extends Flux.Store<any> {
for (const [repo, exts] of Object.entries(ret)) {
try {
for (const ext of exts) {
try {
const existingExtension = this.extensions[ext.id];
if (existingExtension !== undefined) {
if (this.hasUpdate(repo, ext, existingExtension)) {
this.updates[ext.id] = {
version: ext.version!,
download: ext.download
};
}

this.extensions[ext.id].manifest = ext;
this.extensions[ext.id].source = {
type: ExtensionLoadSource.Normal,
url: repo
const uniqueId = this.extensionIndex++;
const extensionData = {
id: ext.id,
uniqueId,
manifest: ext,
source: { type: ExtensionLoadSource.Normal, url: repo },
state: ExtensionState.NotDownloaded
};

if (this.alreadyExists(extensionData)) {
if (this.hasUpdate(extensionData)) {
this.updates[uniqueId] = {
version: ext.version!,
download: ext.download
};

continue;
}

this.extensions[ext.id] = {
id: ext.id,
manifest: ext,
source: { type: ExtensionLoadSource.Normal, url: repo },
state: ExtensionState.NotDownloaded
};
} catch (e) {
logger.error(`Error processing extension ${ext.id}`, e);
continue;
}

this.extensions[uniqueId] = extensionData;
}
} catch (e) {
logger.error(`Error processing repository ${repo}`, e);
Expand All @@ -88,18 +77,21 @@ class MoonbaseSettingsStore extends Flux.Store<any> {
});
}

// this logic sucks so bad lol
private hasUpdate(
repo: string,
repoExt: RepositoryManifest,
existing: MoonbaseExtension
) {
private alreadyExists(ext: MoonbaseExtension) {
return Object.values(this.extensions).some(
(e) => e.id === ext.id && e.source.url === ext.source.url
);
}

private hasUpdate(ext: MoonbaseExtension) {
const existing = Object.values(this.extensions).find(
(e) => e.id === ext.id && e.source.url === ext.source.url
);
if (existing == null) return false;

return (
existing.source.type === ExtensionLoadSource.Normal &&
existing.source.url != null &&
existing.source.url === repo &&
repoExt.version != null &&
existing.manifest.version !== repoExt.version
existing.manifest.version !== ext.manifest.version &&
existing.state !== ExtensionState.NotDownloaded
);
}

Expand All @@ -118,47 +110,66 @@ class MoonbaseSettingsStore extends Flux.Store<any> {
return this.modified;
}

getExtension(id: string) {
return this.extensions[id];
getExtension(uniqueId: number) {
return this.extensions[uniqueId];
}

getExtensionUniqueId(id: string) {
return Object.values(this.extensions).find((ext) => ext.id === id)
?.uniqueId;
}

getExtensionConflicting(uniqueId: number) {
const ext = this.getExtension(uniqueId);
if (ext.state !== ExtensionState.NotDownloaded) return false;
return Object.values(this.extensions).some(
(e) =>
e.id === ext.id &&
e.uniqueId !== uniqueId &&
e.state !== ExtensionState.NotDownloaded
);
}

getExtensionName(id: string) {
return Object.prototype.hasOwnProperty.call(this.extensions, id)
? this.extensions[id].manifest.meta?.name ?? id
: id;
getExtensionName(uniqueId: number) {
const ext = this.getExtension(uniqueId);
return ext.manifest.meta?.name ?? ext.id;
}

getExtensionUpdate(id: string) {
return Object.prototype.hasOwnProperty.call(this.updates, id)
? this.updates[id]
: null;
getExtensionUpdate(uniqueId: number) {
return this.updates[uniqueId]?.version;
}

getExtensionEnabled(id: string) {
const val = this.config.extensions[id];
getExtensionEnabled(uniqueId: number) {
const ext = this.getExtension(uniqueId);
if (ext.state === ExtensionState.NotDownloaded) return false;
const val = this.config.extensions[ext.id];
if (val == null) return false;
return typeof val === "boolean" ? val : val.enabled;
}

getExtensionConfig<T>(id: string, key: string): T | undefined {
const defaultValue = this.extensions[id].manifest.settings?.[key]?.default;
getExtensionConfig<T>(uniqueId: number, key: string): T | undefined {
const ext = this.getExtension(uniqueId);
const defaultValue = ext.manifest.settings?.[key]?.default;
const clonedDefaultValue = this.clone(defaultValue);
const cfg = this.config.extensions[id];
const cfg = this.config.extensions[ext.id];

if (cfg == null || typeof cfg === "boolean") return clonedDefaultValue;
return cfg.config?.[key] ?? clonedDefaultValue;
}

getExtensionConfigName(id: string, key: string) {
return this.extensions[id].manifest.settings?.[key]?.displayName ?? key;
getExtensionConfigName(uniqueId: number, key: string) {
const ext = this.getExtension(uniqueId);
return ext.manifest.settings?.[key]?.displayName ?? key;
}

getExtensionConfigDescription(id: string, key: string) {
return this.extensions[id].manifest.settings?.[key]?.description;
getExtensionConfigDescription(uniqueId: number, key: string) {
const ext = this.getExtension(uniqueId);
return ext.manifest.settings?.[key]?.description;
}

setExtensionConfig(id: string, key: string, value: any) {
const oldConfig = this.config.extensions[id];
setExtensionConfig(uniqueId: number, key: string, value: any) {
const ext = this.getExtension(uniqueId);
const oldConfig = this.config.extensions[ext.id];
const newConfig =
typeof oldConfig === "boolean"
? {
Expand All @@ -170,16 +181,17 @@ class MoonbaseSettingsStore extends Flux.Store<any> {
config: { ...(oldConfig?.config ?? {}), [key]: value }
};

this.config.extensions[id] = newConfig;
this.config.extensions[ext.id] = newConfig;
this.modified = this.isModified();
this.emitChange();
}

setExtensionEnabled(id: string, enabled: boolean) {
let val = this.config.extensions[id];
setExtensionEnabled(uniqueId: number, enabled: boolean) {
const ext = this.getExtension(uniqueId);
let val = this.config.extensions[ext.id];

if (val == null) {
this.config.extensions[id] = { enabled };
this.config.extensions[ext.id] = { enabled };
this.modified = this.isModified();
this.emitChange();
return;
Expand All @@ -191,26 +203,26 @@ class MoonbaseSettingsStore extends Flux.Store<any> {
val.enabled = enabled;
}

this.config.extensions[id] = val;
this.config.extensions[ext.id] = val;
this.modified = this.isModified();
this.emitChange();
}

async installExtension(id: string) {
const ext = this.getExtension(id);
async installExtension(uniqueId: number) {
const ext = this.getExtension(uniqueId);
if (!("download" in ext.manifest)) {
throw new Error("Extension has no download URL");
}

this.installing = true;
try {
const url = this.updates[id]?.download ?? ext.manifest.download;
const url = this.updates[uniqueId]?.download ?? ext.manifest.download;
await natives.installExtension(ext.manifest, url, ext.source.url!);
if (ext.state === ExtensionState.NotDownloaded) {
this.extensions[id].state = ExtensionState.Disabled;
this.extensions[uniqueId].state = ExtensionState.Disabled;
}

delete this.updates[id];
delete this.updates[uniqueId];
} catch (e) {
logger.error("Error installing extension:", e);
}
Expand All @@ -219,14 +231,14 @@ class MoonbaseSettingsStore extends Flux.Store<any> {
this.emitChange();
}

async deleteExtension(id: string) {
const ext = this.getExtension(id);
async deleteExtension(uniqueId: number) {
const ext = this.getExtension(uniqueId);
if (ext == null) return;

this.installing = true;
try {
await natives.deleteExtension(ext.id);
this.extensions[id].state = ExtensionState.NotDownloaded;
this.extensions[uniqueId].state = ExtensionState.NotDownloaded;
} catch (e) {
logger.error("Error deleting extension:", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,19 @@ const DangerIcon =
const PanelButton =
spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.default;

export default function ExtensionCard({ id }: { id: string }) {
export default function ExtensionCard({ uniqueId }: { uniqueId: number }) {
const [tab, setTab] = React.useState(ExtensionPage.Info);
const [restartNeeded, setRestartNeeded] = React.useState(false);

const { ext, enabled, busy, update } = Flux.useStateFromStores(
const { ext, enabled, busy, update, conflicting } = Flux.useStateFromStores(
[MoonbaseSettingsStore],
() => {
return {
ext: MoonbaseSettingsStore.getExtension(id),
enabled: MoonbaseSettingsStore.getExtensionEnabled(id),
ext: MoonbaseSettingsStore.getExtension(uniqueId),
enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId),
busy: MoonbaseSettingsStore.busy,
update: MoonbaseSettingsStore.getExtensionUpdate(id)
update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId),
conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId)
};
}
);
Expand Down Expand Up @@ -96,8 +97,9 @@ export default function ExtensionCard({ id }: { id: string }) {
<Button
color={Button.Colors.BRAND}
submitting={busy}
disabled={conflicting}
onClick={() => {
MoonbaseSettingsStore.installExtension(id);
MoonbaseSettingsStore.installExtension(uniqueId);
}}
>
Install
Expand All @@ -116,7 +118,7 @@ export default function ExtensionCard({ id }: { id: string }) {
icon={TrashIcon}
tooltipText="Delete"
onClick={() => {
MoonbaseSettingsStore.deleteExtension(id);
MoonbaseSettingsStore.deleteExtension(uniqueId);
}}
/>
)}
Expand All @@ -126,7 +128,7 @@ export default function ExtensionCard({ id }: { id: string }) {
icon={DownloadIcon}
tooltipText="Update"
onClick={() => {
MoonbaseSettingsStore.installExtension(id);
MoonbaseSettingsStore.installExtension(uniqueId);
}}
/>
)}
Expand All @@ -147,7 +149,7 @@ export default function ExtensionCard({ id }: { id: string }) {
checked={enabled}
onChange={() => {
setRestartNeeded(true);
MoonbaseSettingsStore.setExtensionEnabled(id, !enabled);
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled);
}}
/>
</div>
Expand Down
Loading

0 comments on commit d06944e

Please sign in to comment.