diff --git a/backend/ficsitcli/serverpicker.go b/backend/ficsitcli/serverpicker.go new file mode 100644 index 00000000..1caac45c --- /dev/null +++ b/backend/ficsitcli/serverpicker.go @@ -0,0 +1,120 @@ +package ficsitcli + +import ( + "fmt" + "path/filepath" + "strconv" + + "github.com/satisfactorymodding/ficsit-cli/cli/disk" +) + +var ServerPicker = &serverPicker{ + disks: make(map[string]disk.Disk), +} + +type serverPicker struct { + disks map[string]disk.Disk + nextServerPickerID int +} + +type PickerDirectory struct { + Name string `json:"name"` + IsValidInstall bool `json:"isValidInstall"` +} + +type PickerResult struct { + IsValidInstall bool `json:"isValidInstall"` + Items []PickerDirectory `json:"items"` +} + +func (s *serverPicker) getID() string { + id := s.nextServerPickerID + s.nextServerPickerID++ + return strconv.Itoa(id) +} + +func (s *serverPicker) StartPicker(path string) (string, error) { + id := s.getID() + + d, err := disk.FromPath(path) + if err != nil { + return "", fmt.Errorf("failed to create: %w", err) + } + + s.disks[id] = d + + return id, nil +} + +func (s *serverPicker) StopPicker(id string) error { + if _, ok := s.disks[id]; !ok { + return fmt.Errorf("no such disk: %s", id) + } + delete(s.disks, id) + return nil +} + +func (s *serverPicker) TryPick(id string, path string) (PickerResult, error) { + d, ok := s.disks[id] + if !ok { + return PickerResult{}, fmt.Errorf("no such disk: %s", id) + } + + result := PickerResult{ + Items: make([]PickerDirectory, 0), + } + + var err error + + result.IsValidInstall, err = isValidInstall(d, path) + if err != nil { + return PickerResult{}, fmt.Errorf("failed to check if valid install: %w", err) + } + + entries, err := d.ReadDir(path) + if err != nil { + return PickerResult{}, fmt.Errorf("failed reading directory: %w", err) + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + validInstall, err := isValidInstall(d, filepath.Join(path, entry.Name())) + if err != nil { + return PickerResult{}, fmt.Errorf("failed to check if valid install: %w", err) + } + + result.Items = append(result.Items, PickerDirectory{ + Name: entry.Name(), + IsValidInstall: validInstall, + }) + } + + return result, nil +} + +func isValidInstall(d disk.Disk, path string) (bool, error) { + var exists bool + var err error + + exists, err = d.Exists(filepath.Join(path, "FactoryServer.sh")) + if !exists { + if err != nil { + return false, fmt.Errorf("failed reading FactoryServer.sh: %w", err) + } + } else { + return true, nil + } + + exists, err = d.Exists(filepath.Join(path, "FactoryServer.exe")) + if !exists { + if err != nil { + return false, fmt.Errorf("failed reading FactoryServer.exe: %w", err) + } + } else { + return true, nil + } + + return false, nil +} diff --git a/frontend/src/lib/components/RemoteServerPicker.svelte b/frontend/src/lib/components/RemoteServerPicker.svelte new file mode 100644 index 00000000..9a4bd9a1 --- /dev/null +++ b/frontend/src/lib/components/RemoteServerPicker.svelte @@ -0,0 +1,187 @@ + + +
+
+ + {#if !disabled} +
+ {#each actualDisplayedItems as item} + + {/each} +
+ {#if validError && !pendingValidCheck && !error} +
Failed to check if selected path is a valid server
+ {/if} + {/if} +
+ {#if (((pendingDisplay || pendingValidCheck) && !valid) || error) && !disabled} +
+ {#if ((pendingDisplay || pendingValidCheck) && !valid)} + + {:else} +
Failed to list directory
+ {/if} +
+ {/if} +
diff --git a/frontend/src/lib/components/modals/ServerManager.svelte b/frontend/src/lib/components/modals/ServerManager.svelte index 4e418de5..0ac22a39 100644 --- a/frontend/src/lib/components/modals/ServerManager.svelte +++ b/frontend/src/lib/components/modals/ServerManager.svelte @@ -2,6 +2,7 @@ import { mdiAlert, mdiLoading, mdiServerNetwork, mdiTrashCan } from '@mdi/js'; import _ from 'lodash'; + import RemoteServerPicker from '$lib/components/RemoteServerPicker.svelte'; import SvgIcon from '$lib/components/SVGIcon.svelte'; import Select from '$lib/components/Select.svelte'; import Tooltip from '$lib/components/Tooltip.svelte'; @@ -62,7 +63,17 @@ return newRemoteType.protocol + authString + '@' + newServerHost + ':' + actualPort + '/' + trimmedPath; })(); - $: isValid = (() => { + $: baseServerPath = (() => { + if (newRemoteType.type === 'local') { + return newServerPath; + } + if (advancedMode) { + return newRemoteType.protocol + newServerPath; + } + return newRemoteType.protocol + authString + '@' + newServerHost + ':' + actualPort; + })(); + + $: isBaseValid = (() => { if (newRemoteType.type === 'local') { return newServerPath.length > 0; } @@ -72,6 +83,18 @@ return newServerUsername.length > 0 && newServerHost.length > 0; })(); + let isPathValid = false; + + $: isValid = (() => { + if (newRemoteType.type === 'local') { + return newServerPath.length > 0; + } + if (advancedMode) { + return newServerPath.length > 0; + } + return newServerUsername.length > 0 && newServerHost.length > 0 && isPathValid; + })(); + async function addNewRemoteServer() { if (!isValid) { return; @@ -196,8 +219,8 @@ -
-
+
+