mirror of
https://github.com/unraid/api.git
synced 2025-12-31 05:29:48 -06:00
fix: recreate watcher on path change (#1203)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved the notifications system by refreshing the monitoring process only when configuration changes occur, leading to a more reliable experience. - **Chores** - Updated internal synchronization timestamps in multiple files to ensure consistency and accurate tracking of recent events. - Removed logging functionality for notifications state to streamline the logging process. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -8,7 +8,6 @@ import { dynamicRemoteAccessReducer } from '@app/store/modules/dynamic-remote-ac
|
||||
import { dynamix } from '@app/store/modules/dynamix.js';
|
||||
import { emhttp } from '@app/store/modules/emhttp.js';
|
||||
import { mothership } from '@app/store/modules/minigraph.js';
|
||||
import { notificationReducer } from '@app/store/modules/notifications.js';
|
||||
import { paths } from '@app/store/modules/paths.js';
|
||||
import { registration } from '@app/store/modules/registration.js';
|
||||
import { remoteGraphQLReducer } from '@app/store/modules/remote-graphql.js';
|
||||
@@ -23,7 +22,6 @@ export const store = configureStore({
|
||||
emhttp: emhttp.reducer,
|
||||
registration: registration.reducer,
|
||||
remoteGraphQL: remoteGraphQLReducer,
|
||||
notifications: notificationReducer,
|
||||
cache: cache.reducer,
|
||||
docker: docker.reducer,
|
||||
upnp: upnp.reducer,
|
||||
@@ -46,7 +44,6 @@ export const getters = {
|
||||
dynamix: () => store.getState().dynamix,
|
||||
emhttp: () => store.getState().emhttp,
|
||||
minigraph: () => store.getState().minigraph,
|
||||
notifications: () => store.getState().notifications,
|
||||
paths: () => store.getState().paths,
|
||||
registration: () => store.getState().registration,
|
||||
remoteGraphQL: () => store.getState().remoteGraphQL,
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { logger } from '@app/core/log.js';
|
||||
import { startAppListening } from '@app/store/listeners/listener-middleware.js';
|
||||
import { clearAllNotifications } from '@app/store/modules/notifications.js';
|
||||
|
||||
export const enableNotificationPathListener = () =>
|
||||
startAppListening({
|
||||
predicate(_, currentState, previousState) {
|
||||
if (
|
||||
currentState.dynamix.notify?.path !== '' &&
|
||||
previousState.dynamix.notify?.path !== currentState.dynamix.notify?.path
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
async effect(_, { dispatch }) {
|
||||
logger.debug('Notification Path Changed or Loaded, Recreating Watcher');
|
||||
dispatch(clearAllNotifications());
|
||||
},
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import type { Notification } from '@app/graphql/generated/api/types.js';
|
||||
import { logger } from '@app/core/log.js';
|
||||
import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
|
||||
import { type NotificationIni } from '@app/core/types/states/notification.js';
|
||||
import { parseConfig } from '@app/core/utils/misc/parse-config.js';
|
||||
import { NotificationSchema } from '@app/graphql/generated/api/operations.js';
|
||||
import { Importance, NotificationType } from '@app/graphql/generated/api/types.js';
|
||||
import { type AppDispatch, type RootState } from '@app/store/index.js';
|
||||
|
||||
interface NotificationState {
|
||||
notifications: Record<string, Notification>;
|
||||
}
|
||||
|
||||
const notificationInitialState: NotificationState = {
|
||||
notifications: {},
|
||||
};
|
||||
|
||||
const fileImportanceToGqlImportance = (importance: NotificationIni['importance']): Importance => {
|
||||
switch (importance) {
|
||||
case 'alert':
|
||||
return Importance.ALERT;
|
||||
case 'warning':
|
||||
return Importance.WARNING;
|
||||
default:
|
||||
return Importance.INFO;
|
||||
}
|
||||
};
|
||||
|
||||
const parseNotificationDateToIsoDate = (unixStringSeconds: string | undefined): string | null => {
|
||||
if (unixStringSeconds && !isNaN(Number(unixStringSeconds))) {
|
||||
return new Date(Number(unixStringSeconds) * 1_000).toISOString();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const loadNotification = createAsyncThunk<
|
||||
{ id: string; notification: Notification },
|
||||
{ path: string },
|
||||
{ state: RootState; dispatch: AppDispatch }
|
||||
>('notifications/loadNotification', ({ path }) => {
|
||||
const notificationFile = parseConfig<NotificationIni>({
|
||||
filePath: path,
|
||||
type: 'ini',
|
||||
});
|
||||
|
||||
const notification: Notification = {
|
||||
id: path,
|
||||
title: notificationFile.event,
|
||||
subject: notificationFile.subject,
|
||||
description: notificationFile.description ?? '',
|
||||
importance: fileImportanceToGqlImportance(notificationFile.importance),
|
||||
link: notificationFile.link,
|
||||
timestamp: parseNotificationDateToIsoDate(notificationFile.timestamp),
|
||||
type: NotificationType.UNREAD,
|
||||
};
|
||||
const convertedNotification = NotificationSchema().parse(notification);
|
||||
|
||||
if (convertedNotification) {
|
||||
pubsub.publish(PUBSUB_CHANNEL.NOTIFICATION, { notificationAdded: convertedNotification });
|
||||
return { id: path, notification: convertedNotification };
|
||||
}
|
||||
throw new Error('Failed to parse notification');
|
||||
});
|
||||
|
||||
export const notificationsStore = createSlice({
|
||||
name: 'notifications',
|
||||
initialState: notificationInitialState,
|
||||
reducers: {
|
||||
clearNotification: (state, action: PayloadAction<{ path: string }>) => {
|
||||
if (state.notifications[action.payload.path]) {
|
||||
delete state.notifications[action.payload.path];
|
||||
}
|
||||
},
|
||||
clearAllNotifications: (state) => {
|
||||
state.notifications = {};
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(loadNotification.fulfilled, (state, { payload }) => {
|
||||
state.notifications[payload.id] = payload.notification;
|
||||
});
|
||||
builder.addCase(loadNotification.rejected, (_, action) => {
|
||||
logger.debug('Failed to load notification with error %o', action.error);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const notificationReducer = notificationsStore.reducer;
|
||||
export const { clearNotification, clearAllNotifications } = notificationsStore.actions;
|
||||
@@ -41,10 +41,6 @@ export const startStoreSync = async () => {
|
||||
join(state.paths.states, 'graphql.log'),
|
||||
JSON.stringify(state.minigraph, null, 2)
|
||||
);
|
||||
writeFileSync(
|
||||
join(state.paths.states, 'notifications.log'),
|
||||
JSON.stringify(state.notifications, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
lastState = state;
|
||||
|
||||
@@ -33,6 +33,10 @@ import { batchProcess, formatDatetime, isFulfilled, isRejected, unraidTimestamp
|
||||
export class NotificationsService {
|
||||
private logger = new Logger(NotificationsService.name);
|
||||
private static watcher: FSWatcher | null = null;
|
||||
/**
|
||||
* The path to the notification directory - will be updated if the user changes the notifier path
|
||||
*/
|
||||
private path: string | null = null;
|
||||
|
||||
private static overview: NotificationOverview = {
|
||||
unread: {
|
||||
@@ -50,7 +54,8 @@ export class NotificationsService {
|
||||
};
|
||||
|
||||
constructor() {
|
||||
NotificationsService.watcher = this.getNotificationsWatcher();
|
||||
this.path = getters.dynamix().notify!.path;
|
||||
void this.getNotificationsWatcher(this.path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,6 +68,13 @@ export class NotificationsService {
|
||||
*/
|
||||
public paths(): Record<'basePath' | NotificationType, string> {
|
||||
const basePath = getters.dynamix().notify!.path;
|
||||
|
||||
if (this.path !== basePath) {
|
||||
// Recreate the watcher with force = true
|
||||
void this.getNotificationsWatcher(basePath, true);
|
||||
this.path = basePath;
|
||||
}
|
||||
|
||||
const makePath = (type: NotificationType) => join(basePath, type.toLowerCase());
|
||||
return {
|
||||
basePath,
|
||||
@@ -78,12 +90,11 @@ export class NotificationsService {
|
||||
* events to their event handlers.
|
||||
*------------------------------------------------------------------------**/
|
||||
|
||||
private getNotificationsWatcher() {
|
||||
const { basePath } = this.paths();
|
||||
|
||||
if (NotificationsService.watcher) {
|
||||
private async getNotificationsWatcher(basePath: string, recreate = false): Promise<FSWatcher> {
|
||||
if (NotificationsService.watcher && !recreate) {
|
||||
return NotificationsService.watcher;
|
||||
}
|
||||
await NotificationsService.watcher?.close().catch((e) => this.logger.error(e));
|
||||
|
||||
NotificationsService.watcher = watch(basePath, { usePolling: CHOKIDAR_USEPOLLING }).on(
|
||||
'add',
|
||||
|
||||
@@ -1 +1 @@
|
||||
1739911366509
|
||||
1740588065597
|
||||
@@ -1 +1 @@
|
||||
1739911365729
|
||||
1740588063495
|
||||
@@ -1 +1 @@
|
||||
1739911366308
|
||||
1740588063858
|
||||
@@ -1 +1 @@
|
||||
1739911366763
|
||||
1740588065854
|
||||
Reference in New Issue
Block a user