Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/AppController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class AppController {
Broadcasters.titleClickedMessageBroadcaster?.unsubscribe(this.onRightClick);
this._appSettingsModel.unsubscribe(this.onSettingChanged);
this._devicePresenter.saveStats();
this._devicePresenter.deinit();
this._appSettingsModel.deinit();
this.uninstallTimers();
}
Expand Down
95 changes: 62 additions & 33 deletions lib/net/DeviceMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class DeviceMonitor {
private _defaultGw: string = "";
private _netMgrSignals: number[] = [];
private _netMgrStateChangeSignals: SignalConnection[] = [];
private _reloadTimeout: number | null = null;

constructor(private _logger: Logger) {
this._textDecoder = new TextDecoder();
Expand Down Expand Up @@ -161,24 +162,12 @@ export class DeviceMonitor {
* It connects to the network manager signals to get the device changes.
*/
init(): void {
this._netMgrSignals.push(
this._client.connect("any-device-added", this._deviceChanged.bind(this))
);
this._netMgrSignals.push(
this._client.connect("any-device-removed", this._deviceChanged.bind(this))
);
this._netMgrSignals.push(
this._client.connect("device-added", this._deviceChanged.bind(this))
);
this._netMgrSignals.push(
this._client.connect("device-removed", this._deviceChanged.bind(this))
);
this._netMgrSignals.push(
this._client.connect("connection-added", this._connectionChanged.bind(this))
);
this._netMgrSignals.push(
this._client.connect("connection-removed", this._connectionChanged.bind(this))
);
this._netMgrSignals.push(
this._client.connect("active-connection-added", this._connectionChanged.bind(this))
);
Expand All @@ -197,10 +186,16 @@ export class DeviceMonitor {
* It disconnects from the network manager signals.
*/
deinit(): void {
if (this._reloadTimeout) {
GLib.source_remove(this._reloadTimeout);
this._reloadTimeout = null;
}
this._disconnectDeviceStateChangeSignals();
this._netMgrSignals.forEach((sigId) => {
this._client.disconnect(sigId);
});
this._netMgrSignals = [];
this._devices = {};
}

/**
Expand All @@ -212,6 +207,7 @@ export class DeviceMonitor {
private _loadDevices(): void {
// disconnect "state-changed" signals of previously stored devices.
this._disconnectDeviceStateChangeSignals();
this._devices = {};

const fileContent = GLib.file_get_contents("/proc/net/dev");
const lines = this._textDecoder.decode(fileContent[1]).split("\n");
Expand All @@ -228,21 +224,29 @@ export class DeviceMonitor {
devices.push(deviceName);
}
for (const name of devices) {
const deviceObj = this._client.get_device_by_iface(name);
const addresses = this._getIPAddress(deviceObj, GLib.SYSDEF_AF_INET);
const type = this.getDeviceType(deviceObj);
const active = this.isActive(deviceObj);
const metered = this.isMetered(deviceObj);
const dummy = this.isDummy(deviceObj);
this._devices[name] = {
name,
type,
device: deviceObj,
ip: addresses[0] || "",
active,
metered,
dummy
};
try {
const deviceObj = this._client.get_device_by_iface(name);
if (deviceObj == null) {
// Skip interfaces not managed by NetworkManager (e.g. Docker veth pairs)
continue;
}
const addresses = this._getIPAddress(deviceObj, GLib.SYSDEF_AF_INET);
const type = this.getDeviceType(deviceObj);
const active = this.isActive(deviceObj);
const metered = this.isMetered(deviceObj);
const dummy = this.isDummy(deviceObj);
this._devices[name] = {
name,
type,
device: deviceObj,
ip: addresses[0] || "",
active,
metered,
dummy
};
} catch (e) {
this._logger.info(`Skipping device '${name}': ${e}`);
}
}

// connect "state-changed" signals of new stored devices.
Expand Down Expand Up @@ -298,28 +302,43 @@ export class DeviceMonitor {
this._netMgrStateChangeSignals = [];
}

/**
* Schedules a debounced reload of devices.
* Multiple rapid signal callbacks are collapsed into a single reload.
*/
private _scheduleReload(): void {
if (this._reloadTimeout) {
GLib.source_remove(this._reloadTimeout);
}
this._reloadTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
this._reloadTimeout = null;
this._loadDevices();
return GLib.SOURCE_REMOVE;
});
}

/**
* Handles the device state changed signal.
* It reloads the devices.
*/
private _deviceStateChanged(): void {
this._loadDevices();
this._scheduleReload();
}

/**
* Handles the device changed signal.
* It reloads the devices.
*/
private _deviceChanged(): void {
this._loadDevices();
this._scheduleReload();
}

/**
* Handles the connection changed signal.
* It reloads the devices.
*/
private _connectionChanged(): void {
this._loadDevices();
this._scheduleReload();
}

/**
Expand All @@ -328,11 +347,21 @@ export class DeviceMonitor {
* @param family - IP address family
* @returns IP addresses of the device.
*/
private _getIPAddress(device: NMDevice, family: number): string[] {
private _getIPAddress(device: NMDevice | null, family: number): string[] {
const addresses: string[] = [];
if (device == null) {
addresses[0] = "-";
return addresses;
}
let ipConfig: NM.IPConfig | null = null;
if (family == GLib.SYSDEF_AF_INET) ipConfig = device.get_ip4_config();
else ipConfig = device.get_ip6_config();
try {
if (family == GLib.SYSDEF_AF_INET) ipConfig = device.get_ip4_config();
else ipConfig = device.get_ip6_config();
} catch (e) {
this._logger.info(`Failed to get IP config for device '${device.get_iface()}': ${e}`);
addresses[0] = "-";
return addresses;
}

if (ipConfig == null) {
this._logger.info(`No config found for device '${device.get_iface()}'`);
Expand Down
15 changes: 12 additions & 3 deletions lib/net/DevicePresenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,16 @@ export class DevicePresenter {
}
this._stats = stats;
}
Broadcasters.deviceResetMessageBroadcaster?.subscribe(this.resetDeviceStats.bind(this));
Broadcasters.deviceResetMessageBroadcaster?.subscribe(this.resetDeviceStats);
}

/**
* Deinitializes the device presenter.
* It unsubscribes from broadcasters and deinitializes the device monitor.
*/
deinit(): void {
Broadcasters.deviceResetMessageBroadcaster?.unsubscribe(this.resetDeviceStats);
this._deviceMonitor.deinit();
}

/**
Expand Down Expand Up @@ -438,7 +447,7 @@ export class DevicePresenter {
* Reset the stats for a specific device.
* It updates the stats in memory and also in the settings.
*/
resetDeviceStats({ name }: { name: string }): void {
resetDeviceStats = ({ name }: { name: string }): void => {
const now = new Date();
this._logger.info(`Resetting the device ${name} at ${now.toString()}`);
if (this._stats[name]) {
Expand All @@ -457,7 +466,7 @@ export class DevicePresenter {
};
this._appSettingsModel.replaceDeviceInfo(name, deviceLogs);
}
}
};

/**
* Reset all devices stats. Remove all the devices which are not active.
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"sourceMap": false,
"strict": true,
"target": "ES2022",
"lib": ["ES2022"]
"lib": ["ES2022"],
"skipLibCheck": true
},
"include": ["ambient.d.ts"],
"files": ["extension.ts", "prefs.ts"]
Expand Down