Add file picker plugin install, plugin installs to developer page (#405)

This commit is contained in:
EMERALD
2023-04-25 21:20:39 -05:00
committed by GitHub
parent d6f336d84b
commit 93151e4e5e
5 changed files with 163 additions and 110 deletions
+22 -3
View File
@@ -139,6 +139,10 @@ class PluginBrowser:
self.loader.watcher.disabled = False self.loader.watcher.disabled = False
async def _install(self, artifact, name, version, hash): async def _install(self, artifact, name, version, hash):
# Will be set later in code
res_zip = None
# Check if plugin is installed
isInstalled = False isInstalled = False
if self.loader.watcher: if self.loader.watcher:
self.loader.watcher.disabled = True self.loader.watcher.disabled = True
@@ -148,7 +152,13 @@ class PluginBrowser:
isInstalled = True isInstalled = True
except: except:
logger.error(f"Failed to determine if {name} is already installed, continuing anyway.") logger.error(f"Failed to determine if {name} is already installed, continuing anyway.")
logger.info(f"Installing {name} (Version: {version})")
# Check if the file is a local file or a URL
if artifact.startswith("file://"):
logger.info(f"Installing {name} from local ZIP file (Version: {version})")
res_zip = BytesIO(open(artifact[7:], "rb").read())
else:
logger.info(f"Installing {name} from URL (Version: {version})")
async with ClientSession() as client: async with ClientSession() as client:
logger.debug(f"Fetching {artifact}") logger.debug(f"Fetching {artifact}")
res = await client.get(artifact, ssl=get_ssl_context()) res = await client.get(artifact, ssl=get_ssl_context())
@@ -157,12 +167,23 @@ class PluginBrowser:
data = await res.read() data = await res.read()
logger.debug(f"Read {len(data)} bytes") logger.debug(f"Read {len(data)} bytes")
res_zip = BytesIO(data) res_zip = BytesIO(data)
else:
logger.fatal(f"Could not fetch from URL. {await res.text()}")
# Check to make sure we got the file
if res_zip is None:
logger.fatal(f"Could not fetch {artifact}")
return
# If plugin is installed, uninstall it
if isInstalled: if isInstalled:
try: try:
logger.debug("Uninstalling existing plugin...") logger.debug("Uninstalling existing plugin...")
await self.uninstall_plugin(name) await self.uninstall_plugin(name)
except: except:
logger.error(f"Plugin {name} could not be uninstalled.") logger.error(f"Plugin {name} could not be uninstalled.")
# Install the plugin
logger.debug("Unzipping...") logger.debug("Unzipping...")
ret = self._unzip_to_plugin_dir(res_zip, name, hash) ret = self._unzip_to_plugin_dir(res_zip, name, hash)
if ret: if ret:
@@ -187,8 +208,6 @@ class PluginBrowser:
self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})") self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
if self.loader.watcher: if self.loader.watcher:
self.loader.watcher.disabled = False self.loader.watcher.disabled = False
else:
logger.fatal(f"Could not fetch from URL. {await res.text()}")
async def request_plugin_install(self, artifact, name, version, hash): async def request_plugin_install(self, artifact, name, version, hash):
request_id = str(time()) request_id = str(time())
@@ -29,10 +29,10 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
strTitle={`Install ${artifact}`} strTitle={`Install ${artifact}`}
strOKButtonText={loading ? 'Installing' : 'Install'} strOKButtonText={loading ? 'Installing' : 'Install'}
> >
{hash == 'False' ? ( Are you sure you want to install {artifact}
<h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3> {version ? ` ${version}` : ''}?
) : ( {hash == 'False' && (
`Are you sure you want to install ${artifact} ${version}?` <span style={{ color: 'red' }}> This plugin does not have a hash, you are installing it at your own risk.</span>
)} )}
</ConfirmModal> </ConfirmModal>
); );
@@ -134,7 +134,15 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
)} )}
</div> </div>
)} )}
<span
style={{
textOverflow: 'ellipsis',
overflow: 'hidden',
textAlign: 'left',
}}
>
{file.name} {file.name}
</span>
</div> </div>
</DialogButton> </DialogButton>
); );
@@ -1,19 +1,63 @@
import { DialogBody, Field, TextField, Toggle } from 'decky-frontend-lib'; import {
import { useRef } from 'react'; DialogBody,
import { FaReact, FaSteamSymbol } from 'react-icons/fa'; DialogButton,
DialogControlsSection,
DialogControlsSectionHeader,
Field,
TextField,
Toggle,
} from 'decky-frontend-lib';
import { useRef, useState } from 'react';
import { FaFileArchive, FaLink, FaReact, FaSteamSymbol } from 'react-icons/fa';
import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer'; import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer';
import { installFromURL } from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting'; import { useSetting } from '../../../../utils/hooks/useSetting';
import RemoteDebuggingSettings from '../general/RemoteDebugging'; import RemoteDebuggingSettings from '../general/RemoteDebugging';
const installFromZip = () => {
window.DeckyPluginLoader.openFilePicker('/home/deck', true).then((val) => {
const url = `file://${val.path}`;
console.log(`Installing plugin locally from ${url}`);
if (url.endsWith('.zip')) {
installFromURL(url);
} else {
window.DeckyPluginLoader.toaster.toast({
title: 'Decky',
body: `Installation failed! Only ZIP files are supported.`,
onClick: installFromZip,
});
}
});
};
export default function DeveloperSettings() { export default function DeveloperSettings() {
const [enableValveInternal, setEnableValveInternal] = useSetting<boolean>('developer.valve_internal', false); const [enableValveInternal, setEnableValveInternal] = useSetting<boolean>('developer.valve_internal', false);
const [reactDevtoolsEnabled, setReactDevtoolsEnabled] = useSetting<boolean>('developer.rdt.enabled', false); const [reactDevtoolsEnabled, setReactDevtoolsEnabled] = useSetting<boolean>('developer.rdt.enabled', false);
const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', ''); const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', '');
const [pluginURL, setPluginURL] = useState('');
const textRef = useRef<HTMLDivElement>(null); const textRef = useRef<HTMLDivElement>(null);
return ( return (
<DialogBody> <DialogBody>
<DialogControlsSection>
<DialogControlsSectionHeader>Third-Party Plugins</DialogControlsSectionHeader>
<Field label="Install Plugin from ZIP File" icon={<FaFileArchive style={{ display: 'block' }} />}>
<DialogButton onClick={installFromZip}>Browse</DialogButton>
</Field>
<Field
label="Install Plugin from URL"
description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />}
icon={<FaLink style={{ display: 'block' }} />}
>
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
Install
</DialogButton>
</Field>
</DialogControlsSection>
<DialogControlsSection>
<DialogControlsSectionHeader>Other</DialogControlsSectionHeader>
<RemoteDebuggingSettings /> <RemoteDebuggingSettings />
<Field <Field
label="Enable Valve Internal" label="Enable Valve Internal"
@@ -38,8 +82,8 @@ export default function DeveloperSettings() {
description={ description={
<> <>
<span style={{ whiteSpace: 'pre-line' }}> <span style={{ whiteSpace: 'pre-line' }}>
Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set the Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set
IP address before enabling. the IP address before enabling.
</span> </span>
<br /> <br />
<br /> <br />
@@ -59,6 +103,7 @@ export default function DeveloperSettings() {
}} }}
/> />
</Field> </Field>
</DialogControlsSection>
</DialogBody> </DialogBody>
); );
} }
@@ -1,15 +1,5 @@
import { import { DialogBody, DialogControlsSection, DialogControlsSectionHeader, Field, Toggle } from 'decky-frontend-lib';
DialogBody,
DialogButton,
DialogControlsSection,
DialogControlsSectionHeader,
Field,
TextField,
Toggle,
} from 'decky-frontend-lib';
import { useState } from 'react';
import { installFromURL } from '../../../../store';
import { useDeckyState } from '../../../DeckyState'; import { useDeckyState } from '../../../DeckyState';
import BranchSelect from './BranchSelect'; import BranchSelect from './BranchSelect';
import StoreSelect from './StoreSelect'; import StoreSelect from './StoreSelect';
@@ -22,7 +12,6 @@ export default function GeneralSettings({
isDeveloper: boolean; isDeveloper: boolean;
setIsDeveloper: (val: boolean) => void; setIsDeveloper: (val: boolean) => void;
}) { }) {
const [pluginURL, setPluginURL] = useState('');
const { versionInfo } = useDeckyState(); const { versionInfo } = useDeckyState();
return ( return (
@@ -46,14 +35,6 @@ export default function GeneralSettings({
}} }}
/> />
</Field> </Field>
<Field
label="Install plugin from URL"
description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />}
>
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
Install
</DialogButton>
</Field>
</DialogControlsSection> </DialogControlsSection>
<DialogControlsSection> <DialogControlsSection>
<DialogControlsSectionHeader>About</DialogControlsSectionHeader> <DialogControlsSectionHeader>About</DialogControlsSectionHeader>