Refactor Polling Mechanism: Implement Class-Based Poller with Start/Stop Control (#528)

* custom module updates to class based.

* Finish poller logic.

* Use composition for pollers

* Rename poller

* Handle recorder polling.

* Fix quotes in bash command

* Remove logs
This commit is contained in:
Jas Singh
2024-11-23 03:55:00 -08:00
committed by GitHub
parent c10c9d0e93
commit a4f5fb5917
26 changed files with 460 additions and 169 deletions

90
lib/poller/BashPoller.ts Normal file
View File

@@ -0,0 +1,90 @@
import { Variable as VariableType } from 'types/variable';
import { Bind } from 'lib/types/variable';
import { GenericFunction } from 'lib/types/customModules/generic';
import { BarModule } from 'lib/types/options';
import { Poller } from './Poller';
/**
* A class that manages polling of a variable by executing a bash command at specified intervals.
*/
export class BashPoller<Value, Parameters extends unknown[]> {
private poller: Poller;
private params: Parameters;
/**
* Creates an instance of BashPoller.
*
* @param targetVariable - The target variable to poll.
* @param trackers - An array of trackers to monitor.
* @param pollingInterval - The interval at which polling occurs.
* @param updateCommand - The command to update the target variable.
* @param pollingFunction - The function to execute during each poll.
* @param params - Additional parameters for the polling function.
*
* @example
*
* ```ts
* //##################### EXAMPLE ##########################
* const updatesPoller = new BashPoller<string, []>(
* pendingUpdates,
* [padZero.bind('value'), postInputUpdater.bind('value')],
* pollingInterval.bind('value'),
* updateCommand.value,
* processUpdateCount,
* );
* //#######################################################
*
* ```
*/
constructor(
private targetVariable: VariableType<Value>,
private trackers: Bind[],
private pollingInterval: Bind,
private updateCommand: string,
private pollingFunction: GenericFunction<Value, [string, ...Parameters]>,
...params: Parameters
) {
this.params = params;
this.poller = new Poller(this.pollingInterval, this.trackers, this.execute);
}
/**
* Executes the bash command specified in the updateCommand property.
*
* The result of the command is processed by the pollingFunction and
* assigned to the targetVariable.
*/
public execute = async (): Promise<void> => {
try {
const res = await Utils.execAsync(`bash -c "${this.updateCommand}"`);
this.targetVariable.value = await this.pollingFunction(res, ...this.params);
} catch (error) {
console.error(`Error executing bash command "${this.updateCommand}":`, error);
}
};
/**
* Starts the polling process.
*/
public start(): void {
this.poller.start();
}
/**
* Stops the polling process.
*/
public stop(): void {
this.poller.stop();
}
/**
* Initializes the poller with the specified module.
*
* @param moduleName - The name of the module to initialize.
*/
public initialize(moduleName?: BarModule): void {
this.poller.initialize(moduleName);
}
}

View File

@@ -0,0 +1,86 @@
import { Variable as VariableType } from 'types/variable';
import { Bind } from 'lib/types/variable';
import { GenericFunction } from 'lib/types/customModules/generic';
import { BarModule } from 'lib/types/options';
import { Poller } from './Poller';
/**
* A class that manages polling of a variable by executing a generic function at specified intervals.
*/
export class FunctionPoller<Value, Parameters extends unknown[] = []> {
private poller: Poller;
private params: Parameters;
/**
* Creates an instance of FunctionPoller.
*
* @param targetVariable - The target variable to poll.
* @param trackers - An array of trackers to monitor.
* @param pollingInterval - The interval at which polling occurs.
* @param pollingFunction - The function to execute during each poll.
* @param params - Additional parameters for the polling function.
*
* @example
*
* ```ts
* //##################### EXAMPLE ##########################
* const cpuPoller = new FunctionPoller<number, []>(
* cpuUsage,
* [round.bind('value')],
* pollingInterval.bind('value'),
* computeCPU,
* );
* //#######################################################
*
* ```
*/
constructor(
private targetVariable: VariableType<Value>,
private trackers: Bind[],
private pollingInterval: Bind,
private pollingFunction: GenericFunction<Value, Parameters>,
...params: Parameters
) {
this.params = params;
this.poller = new Poller(this.pollingInterval, this.trackers, this.execute);
}
/**
* Executes the polling function with the provided parameters.
*
* The result of the function is assigned to the target variable.
*/
private execute = async (): Promise<void> => {
try {
const result = await this.pollingFunction(...this.params);
this.targetVariable.value = result;
} catch (error) {
console.error('Error executing polling function:', error);
}
};
/**
* Starts the polling process.
*/
public start(): void {
this.poller.start();
}
/**
* Stops the polling process.
*/
public stop(): void {
this.poller.stop();
}
/**
* Initializes the poller with the specified module.
*
* @param moduleName - The name of the module to initialize.
*/
public initialize(moduleName?: BarModule): void {
this.poller.initialize(moduleName);
}
}

107
lib/poller/Poller.ts Normal file
View File

@@ -0,0 +1,107 @@
import GLib from 'gi://GLib?version=2.0';
import { Bind } from 'lib/types/variable';
import { BarModule } from 'lib/types/options';
import { getLayoutItems } from 'lib/utils';
const { layouts } = options.bar;
/**
* A class that manages the polling lifecycle, including interval management and execution state.
*/
export class Poller {
private intervalInstance: number | null = null;
private isExecuting: boolean = false;
private pollingFunction: () => Promise<void>;
/**
* Creates an instance of Poller.
* @param pollingInterval - The interval at which polling occurs.
* @param trackers - An array of trackers to monitor.
* @param pollingFunction - The function to execute during each poll.
*/
constructor(
private pollingInterval: Bind,
private trackers: Bind[],
pollingFunction: () => Promise<void>,
) {
this.pollingFunction = pollingFunction;
}
/**
* Starts the polling process by setting up the interval.
*/
public start(): void {
Utils.merge([this.pollingInterval, ...this.trackers], (intervalMs: number) => {
this.executePolling(intervalMs);
});
}
/**
* Stops the polling process and cleans up resources.
*/
public stop(): void {
if (this.intervalInstance !== null) {
GLib.source_remove(this.intervalInstance);
this.intervalInstance = null;
}
}
/**
* Initializes the polling based on module usage.
*
* If not module is provided then we can safely assume that we want
* to always run the pollig interval.
*
* @param moduleName - The name of the module to initialize.
*/
public initialize(moduleName?: BarModule): void {
if (moduleName === undefined) {
return this.start();
}
const initialModules = getLayoutItems();
if (initialModules.includes(moduleName)) {
this.start();
} else {
this.stop();
}
layouts.connect('changed', () => {
const usedModules = getLayoutItems();
if (usedModules.includes(moduleName)) {
this.start();
} else {
this.stop();
}
});
}
/**
* Executes the polling function at the specified interval.
*
* @param intervalMs - The polling interval in milliseconds.
*/
private executePolling(intervalMs: number): void {
if (this.intervalInstance !== null) {
GLib.source_remove(this.intervalInstance);
}
this.intervalInstance = Utils.interval(intervalMs, async () => {
if (this.isExecuting) {
return;
}
this.isExecuting = true;
try {
await this.pollingFunction();
} catch (error) {
console.error('Error during polling execution:', error);
} finally {
this.isExecuting = false;
}
});
}
}

View File

@@ -1,4 +1,4 @@
export type GenericFunction<T, P extends unknown[] = unknown[]> = (...args: P) => T;
export type GenericFunction<Value, Parameters extends unknown[]> = (...args: Parameters) => Promise<Value> | Value;
export type GenericResourceMetrics = {
total: number;

View File

@@ -15,8 +15,38 @@ export type RecursiveOptionsObject = {
};
export type BarLocation = 'top' | 'bottom';
export type BarModule =
| 'battery'
| 'dashboard'
| 'workspaces'
| 'windowtitle'
| 'media'
| 'notifications'
| 'volume'
| 'network'
| 'bluetooth'
| 'clock'
| 'ram'
| 'cpu'
| 'cputemp'
| 'storage'
| 'netstat'
| 'kbinput'
| 'updates'
| 'submap'
| 'weather'
| 'power'
| 'systray'
| 'hypridle'
| 'hyprsunset';
export type BarLayout = {
[key: string]: Layout;
left: BarModule[];
middle: BarModule[];
right: BarModule[];
};
export type BarLayouts = {
[key: string]: BarLayout;
};
export type Unit = 'imperial' | 'metric';

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type Application } from 'types/service/applications';
import { NotificationAnchor } from './types/options';
import { BarModule, NotificationAnchor } from './types/options';
import { OSDAnchor } from 'lib/types/options';
import icons, { substitutes } from './icons';
import Gtk from 'gi://Gtk?version=3.0';
@@ -18,6 +18,29 @@ import options from 'options';
export type Binding<T> = import('types/service').Binding<any, any, T>;
/**
* Retrieves all unique layout items from the bar options.
*
* @returns An array of unique layout items.
*/
export const getLayoutItems = (): BarModule[] => {
const { layouts } = options.bar;
const itemsInLayout: BarModule[] = [];
Object.keys(layouts.value).forEach((monitor) => {
const leftItems = layouts.value[monitor].left;
const rightItems = layouts.value[monitor].right;
const middleItems = layouts.value[monitor].middle;
itemsInLayout.push(...leftItems);
itemsInLayout.push(...middleItems);
itemsInLayout.push(...rightItems);
});
return [...new Set(itemsInLayout)];
};
/**
* @returns substitute icon || name || fallback icon
*/