Skip to content

Commit

Permalink
chore: progress on microsdeck integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jessebofill committed Nov 27, 2023
1 parent efa60a2 commit 1e32cb4
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 59 deletions.
11 changes: 11 additions & 0 deletions src/components/CustomTabContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class CustomTabContainer implements TabContainer {
collection: Collection;
filtersMode: LogicalMode;
categoriesToInclude: number;
dependsOnMicroSDeck: boolean;

/**
* Creates a new CustomTabContainer.
Expand All @@ -31,6 +32,7 @@ export class CustomTabContainer implements TabContainer {
this.filters = filterSettingsList;
this.filtersMode = filtersMode;
this.categoriesToInclude = categoriesToInclude;
this.dependsOnMicroSDeck = false;

//@ts-ignore
this.collection = {
Expand All @@ -47,6 +49,7 @@ export class CustomTabContainer implements TabContainer {
};

this.buildCollection();
this.checkMicroSDeckDependency();
}

getActualTab(TabContentComponent: TabContentComponent, sortingProps: Omit<TabContentProps, 'collection'>, footer: SteamTab['footer'], collectionAppFilter: any): SteamTab {
Expand Down Expand Up @@ -110,6 +113,14 @@ export class CustomTabContainer implements TabContainer {
this.categoriesToInclude = categoriesToInclude;
this.filters = filters;
this.buildCollection();
this.checkMicroSDeckDependency;
}

/**
* Checks and sets whether or not the tab has filters that depend on MicroSDeck plugin.
*/
checkMicroSDeckDependency() {
this.dependsOnMicroSDeck = this.containsFilterType('sd card');
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/components/filters/FilterSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ const FilterSelectModal: VFC<FilterSelectModalProps> = ({ selectedOption, onSele
"last played": "Selects apps based on when they were last played.",
demo: "Selects apps that are/aren't demos.",
streamable: "Selects apps that can/can't be streamed from another computer.",
"current card": "Selects apps that are present on the current MicroSD Card",
"on card": "Selects apps that are present on a given MicroSD Card"
"sd card": "Selects apps that are present on the inserted/ specific MicroSD Card",
}

useEffect(() => {setTimeout(() => setFocusable(true), 10)}, []);
Expand Down
63 changes: 19 additions & 44 deletions src/components/filters/Filters.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PluginController } from "../../lib/controllers/PluginController";
import { DateIncludes, DateObj } from '../generic/DatePickers';

export type FilterType = 'collection' | 'installed' | 'regex' | 'friends' | 'tags' | 'whitelist' | 'blacklist' | 'merge' | 'platform' | 'deck compatibility' | 'review score' | 'time played' | 'size on disk' | 'release date' | 'last played' | 'demo' | 'streamable' | 'current card' | 'on card';
export type FilterType = 'collection' | 'installed' | 'regex' | 'friends' | 'tags' | 'whitelist' | 'blacklist' | 'merge' | 'platform' | 'deck compatibility' | 'review score' | 'time played' | 'size on disk' | 'release date' | 'last played' | 'demo' | 'streamable' | 'sd card';

export type TimeUnit = 'minutes' | 'hours' | 'days';
export type ThresholdCondition = 'above' | 'below';
Expand Down Expand Up @@ -33,9 +33,8 @@ type SizeOnDiskFilterParams = { gbThreshold: number, condition: ThresholdConditi
type ReleaseDateFilterParams = { date?: DateObj, daysAgo?: number, condition: ThresholdCondition; };
type LastPlayedFilterParams = { date?: DateObj, daysAgo?: number, condition: ThresholdCondition; };
type DemoFilterParams = { isDemo: boolean; };
type StreamableFilterParams = { isStreamable: boolean; }
type CurrentCardParams = {}
type OnCardParams = { cardId: string }
type StreamableFilterParams = { isStreamable: boolean; };
type SdCardParams = { cardId: string } //use 'inserted' for currently inserted card

export type FilterParams<T extends FilterType> =
T extends 'collection' ? CollectionFilterParams :
Expand All @@ -55,8 +54,7 @@ export type FilterParams<T extends FilterType> =
T extends 'last played' ? LastPlayedFilterParams :
T extends 'demo' ? DemoFilterParams :
T extends 'streamable' ? StreamableFilterParams :
T extends 'current card' ? CurrentCardParams :
T extends 'on card' ? OnCardParams :
T extends 'sd card' ? SdCardParams :
never;

export type TabFilterSettings<T extends FilterType> = {
Expand Down Expand Up @@ -89,8 +87,7 @@ export const FilterDefaultParams: { [key in FilterType]: FilterParams<key> } = {
"last played": { date: undefined, condition: 'above' },
"demo": { isDemo: true },
"streamable": { isStreamable: true },
"current card": {},
"on card": { cardId: "" },
"sd card": { cardId: 'inserted' },

};

Expand All @@ -107,6 +104,7 @@ export function canBeInverted(filter: TabFilterSettings<FilterType>): boolean {
case "tags":
case "merge":
case "deck compatibility":
case "sd card":
return true;
case "platform":
case "installed":
Expand All @@ -119,8 +117,6 @@ export function canBeInverted(filter: TabFilterSettings<FilterType>): boolean {
case "last played":
case "demo":
case "streamable":
case "current card":
case "on card":
return false;
}
}
Expand Down Expand Up @@ -158,8 +154,7 @@ export function isValidParams(filter: TabFilterSettings<FilterType>): boolean {
case "size on disk":
case "demo":
case "streamable":
case "current card":
case "on card":
case "sd card":
return true;
}
}
Expand Down Expand Up @@ -265,29 +260,21 @@ export function validateFilter(filter: TabFilterSettings<FilterType>): Validatio
mergeErrorEntries: mergeErrorEntries
};
}
case "on card": {
const cardFilter = filter as TabFilterSettings<'on card'>;
case "sd card": {
const cardFilter = filter as TabFilterSettings<'sd card'>;

const cardsAndGames = MicroSDeck?.CardsAndGames;
let passed = true;
if (PluginController.microSDeckInstalled) {
const cardsAndGames = MicroSDeck?.CardsAndGames;

if(!(cardsAndGames?.length)) {
return {
passed: false,
errors: ["No Cards avaliable"]
}
}

let passed = false;
for(let [card] of cardsAndGames) {
if(cardFilter.params.cardId == card.uid) {
passed = true;
if (!cardsAndGames?.find(([card]) => cardFilter.params.cardId === card.uid)) {
passed = false;
}
}

return {
passed,
errors: passed ? [] : ["Couldn't find the selected card in the list of current cards."]
}
errors: passed ? [] : ["Couldn't find the selected card in the list of known cards."]
};
}
case "regex":
case "friends":
Expand All @@ -304,7 +291,6 @@ export function validateFilter(filter: TabFilterSettings<FilterType>): Validatio
case "last played":
case "demo":
case "streamable":
case "current card":
return {
passed: true,
errors: []
Expand Down Expand Up @@ -446,21 +432,10 @@ export class Filter {
const isStreamable = appOverview.per_client_data.some((clientData) => clientData.client_name !== "This machine" && clientData.installed);
return params.isStreamable ? isStreamable : !isStreamable;
},
//@ts-ignore params is unused
'current card': (params: FilterParams<'current card'>, appOverview: SteamAppOverview) => {
const currentCardAndGames = MicroSDeck?.CurrentCardAndGames;

if(!currentCardAndGames) return false;

const [_, games] = currentCardAndGames;

return !!games.find((game) => +game.uid == appOverview.appid);
},
'on card': (params: FilterParams<'on card'>, appOverview: SteamAppOverview) => {
const cardsAndGames = MicroSDeck?.CardsAndGames;
const card = cardsAndGames?.find(([card]) => card.uid == params.cardId);
'sd card': (params: FilterParams<'sd card'>, appOverview: SteamAppOverview) => {
const card = params.cardId === 'inserted' ? MicroSDeck?.CurrentCardAndGames : MicroSDeck?.CardsAndGames?.find(([card]) => card.uid == params.cardId);

if(!card)return false;
if (!card) return false;

return !!card[1].find((game) => +game.uid == appOverview.appid);
},
Expand Down
9 changes: 6 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ import { DocPage } from "./components/docs/DocsPage";
import { IncludeCategories } from "./lib/Utils";
import { PresetMenu } from './components/menus/PresetMenu';
import { MicroSDeckManager } from "@cebbinghaus/microsdeck";
import { MicroSDeckInterop } from './lib/controllers/MicroSDeckInterop';

declare global {
let DeckyPluginLoader: { pluginReloadQueue: { name: string; version?: string; }[]; };
var MicroSDeck: MicroSDeckManager | undefined;
var SteamClient: SteamClient;
let collectionStore: CollectionStore;
Expand All @@ -65,6 +67,9 @@ interface TabEntryInteractablesProps {
const Content: VFC<{}> = ({ }) => {
const { visibleTabsList, hiddenTabsList, tabsMap, tabMasterManager } = useTabMasterContext();

// MicroSDeckInterop.checkInstallStateChanged();
// const isMicroSDeckInstalled = MicroSDeckInterop.state === 'good';

function TabEntryInteractables({ entry }: TabEntryInteractablesProps) {
const tabContainer = tabsMap.get(entry.data!.id)!;
return (<TabActionsButton {...{ tabContainer, tabMasterManager }} />);
Expand Down Expand Up @@ -235,9 +240,7 @@ export default definePlugin((serverAPI: ServerAPI) => {

PythonInterop.setServer(serverAPI);

const microSDeckManager = window.MicroSDeck = (window.MicroSDeck || new MicroSDeckManager({url: "http://localhost:12412"}));

const tabMasterManager = new TabMasterManager(microSDeckManager);
const tabMasterManager = new TabMasterManager();
PluginController.setup(serverAPI, tabMasterManager);

const loginUnregisterer = PluginController.initOnLogin(async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export function debounce(func:Function, wait:number, immediate?:boolean) {
/**
* Recursive function that checks whether an array of TabFilterSettings contains any filters of a specified type.
* @param filters The array of TabFilterSettings to check in.
* @param filterType The filter types to check are included.
* @param filterTypes The filter types to check are included.
* @returns Boolean
*/
export function filtersHaveType(filters: TabFilterSettings<FilterType>[], ...filterTypes: FilterType[] ) {
Expand Down
64 changes: 64 additions & 0 deletions src/lib/controllers/MicroSDeckInterop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { sleep } from 'decky-frontend-lib';
import { LogController } from './LogController';
import { MicroSDeckManager } from '@cebbinghaus/microsdeck';

export class MicroSDeckInterop {
public static state: 'not installed' | 'version too low' | 'version too high' | 'good' = 'not installed';
public static ref: MicroSDeckManager | undefined;

/**
* Checks if MicroSDeck plugin is installed when loading
*/
//* this is not complete, i have to change it
static async checkInstallStateOnLoad() {
//* add version match verification here
LogController.log("Checking for installation of MicroSDeck...");
//MicroSDeck is already loaded
if (MicroSDeck) {
LogController.log("MicroSDeck is installed");
return true;
} else {
//MicroSDeck is in queue to be loaded, wait til it's removed (starts loading)
while (!!DeckyPluginLoader.pluginReloadQueue.find(plugin => plugin.name === 'MicroSDeck')) {
await sleep(200);
}

//MicroSDeck has either started loading or is not installed at all, wait a little longer to allow it to load.
let tries = 0;
while (!MicroSDeck) {
tries++;
if (tries > 10) {
LogController.log("Could not find MicroSDeck installation");
return false; // if MicroSDeck isn't found after number of attempts, give up
}
await sleep(100);
}

LogController.log("MicroSDeck is installed");
return true;
}
}


static checkInstallStateChanged() {
if (!MicroSDeck) {
this.ref = undefined;
this.state = 'not installed';
} else {
//* window.MicroSDeck = undefined needs to be added back to plugin's onDismount or fire an event there


//MicroSDeck has been reinstalled or reloaded
if (MicroSDeck !== this.ref) {
this.ref = MicroSDeck;

//* resub to new event bus
}
//* check version
}
}

static checkVersion() {

}
}
37 changes: 36 additions & 1 deletion src/lib/controllers/PluginController.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConfirmModal, ServerAPI, showModal } from "decky-frontend-lib";
import { ConfirmModal, ServerAPI, showModal, sleep } from "decky-frontend-lib";
import { PythonInterop } from "./PythonInterop";
import { SteamController } from "./SteamController";
import { LogController } from "./LogController";
Expand Down Expand Up @@ -45,6 +45,7 @@ export class PluginController {
private static tabMasterManager: TabMasterManager;

private static steamController: SteamController;
public static microSDeckInstalled: boolean = false;

/**
* Sets the plugin's serverAPI.
Expand All @@ -65,6 +66,7 @@ export class PluginController {
LogController.log(`User logged in. [DEBUG] username: ${username}.`);
if (await this.steamController.waitForServicesToInitialize()) {
await PluginController.init();
this.microSDeckInstalled = await PluginController.isMicroSDeckInstalledOnLoad();
onMount();
} else {
PythonInterop.toast("Error", "Failed to initialize, try restarting.");
Expand Down Expand Up @@ -124,4 +126,37 @@ export class PluginController {
static onWakeFromSleep() {
this.tabMasterManager.buildTimeBasedFilterTabs();
}

/**
* Checks if MicroSDeck plugin is installed when loading
*/
//* moving to MicroSDeckInterop
static async isMicroSDeckInstalledOnLoad() {
//* add version match verification here
LogController.log("Checking for installation of MicroSDeck...");
//MicroSDeck is already loaded
if (MicroSDeck) {
LogController.log("MicroSDeck is installed");
return true;
} else {
//MicroSDeck is in queue to be loaded, wait til it's removed (starts loading)
while (!!DeckyPluginLoader.pluginReloadQueue.find(plugin => plugin.name === 'MicroSDeck')) {
await sleep(200);
}

//MicroSDeck has either started loading or is not installed at all, wait a little longer to allow it to load.
let tries = 0;
while (!MicroSDeck) {
tries++;
if (tries > 10) {
LogController.log("Could not find MicroSDeck installation");
return false; // if MicroSDeck isn't found after number of attempts, give up
}
await sleep(100);
}

LogController.log("MicroSDeck is installed");
return true;
}
}
}
6 changes: 6 additions & 0 deletions src/patches/LibraryPatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TabMasterManager } from "../state/TabMasterManager";
import { CustomTabContainer } from "../components/CustomTabContainer";
import { LogController } from "../lib/controllers/LogController";
import { LibraryMenu } from '../components/menus/LibraryMenu';
import { MicroSDeckInterop } from '../lib/controllers/MicroSDeckInterop';

/**
* Patches the Steam library to allow the plugin to change the tabs.
Expand All @@ -37,6 +38,8 @@ export const patchLibrary = (serverAPI: ServerAPI, tabMasterManager: TabMasterMa
return innerPatch.unpatch();
});

//MicroSDeckInterop.checkInstallStateChanged();

//* This patch always runs twice
afterPatch(ret1, "type", (_: Record<string, unknown>[], ret2: ReactElement) => {
if (!ret2?.type) {
Expand Down Expand Up @@ -84,6 +87,9 @@ export const patchLibrary = (serverAPI: ServerAPI, tabMasterManager: TabMasterMa
pacthedTabs = tablist.flatMap((tabContainer) => {
if (tabContainer.filters) {
const footer = { ...(tabTemplate!.footer ?? {}), onMenuButton: getShowMenu(tabContainer.id, tabMasterManager), onMenuActionDescription: 'Tab Master' };

//if MicroSDeck isn't installed don't display any tabs that depend on it; return empty array for flat map
//if (!MicroSDeckInterop.state !== 'good' && (tabContainer as CustomTabContainer).dependsOnMicroSDeck) return [];
return (tabContainer as CustomTabContainer).getActualTab(tabContentComponent, sortingProps, footer, collectionsAppFilterGamepad);
} else {
return tabs.find(actualTab => {
Expand Down
Loading

0 comments on commit 1e32cb4

Please sign in to comment.