tutanota/src/common/offline/CachePostLoginAction.ts
sug f11e59672e
improve inbox rule handling and run spam prediction after inbox rules
Instead of applying inbox rules based on the unread mail state in the
inbox folder, we introduce the new ProcessingState enum on
the mail type. If a mail has been processed by the leader client, which
is checking for matching inbox rules, the ProcessingState is
updated. If there is a matching rule the flag is updated through the
MoveMailService, if there is no matching rule, the flag is updated
using the ClientClassifierResultService. Both requests are
throttled / debounced. After processing inbox rules, spam prediction
is conducted for mails that have not yet been moved by an inbox rule.
The ProcessingState for not matching ham mails is also updated using
the ClientClassifierResultService.

This new inbox rule handing solves the following two problems:
 - when clicking on a notification it could still happen,
   that sometimes the inbox rules where not applied
 - when the inbox folder had a lot of unread mails, the loading time did
   massively increase, since inbox rules were re-applied on every load

Co-authored-by: amm <amm@tutao.de>
Co-authored-by: Nick <nif@tutao.de>
Co-authored-by: das <das@tutao.de>
Co-authored-by: abp <abp@tutao.de>
Co-authored-by: jhm <17314077+jomapp@users.noreply.github.com>
Co-authored-by: map <mpfau@users.noreply.github.com>
Co-authored-by: Kinan <104761667+kibibytium@users.noreply.github.com>
2025-10-22 09:40:45 +02:00

53 lines
2.8 KiB
TypeScript

import { LoggedInEvent, LoginController, PostLoginAction } from "../api/main/LoginController.js"
import { CalendarModel } from "../../calendar-app/calendar/model/CalendarModel.js"
import { CalendarEventTypeRef } from "../api/entities/tutanota/TypeRefs.js"
import { CUSTOM_MIN_ID } from "../api/common/utils/EntityUtils.js"
import { EntityClient } from "../api/common/EntityClient.js"
import { ProgressTracker } from "../api/main/ProgressTracker.js"
import { promiseMap } from "@tutao/tutanota-utils"
import { NoopProgressMonitor } from "../api/common/utils/ProgressMonitor.js"
import { SessionType } from "../api/common/SessionType.js"
import { ExposedCacheStorage } from "../api/worker/rest/DefaultEntityRestCache.js"
import { OfflineStorageSettingsModel } from "./OfflineStorageSettingsModel"
export class CachePostLoginAction implements PostLoginAction {
constructor(
private readonly calendarModel: CalendarModel,
private readonly entityClient: EntityClient,
private readonly progressTracker: ProgressTracker,
private readonly cacheStorage: ExposedCacheStorage,
private readonly logins: LoginController,
private readonly offlineStorageSettings: OfflineStorageSettingsModel | null, // null if no cleaning e.g in calendar
) {}
async onFullLoginSuccess(loggedInEvent: LoggedInEvent): Promise<void> {
// we use an ephemeral cache for non-persistent sessions which doesn't
// support or save calendar events, so it's pointless to preload them.
if (loggedInEvent.sessionType !== SessionType.Persistent) return
//
// 3 work to load calendar info, 2 work to load short and long events
const workPerCalendar = 3 + 2
const totalWork = this.logins.getUserController().getCalendarMemberships().length * workPerCalendar
const monitorHandle = await this.progressTracker.registerMonitor(totalWork)
const progressMonitor = this.progressTracker.getMonitor(monitorHandle) ?? new NoopProgressMonitor()
const calendarInfos = await this.calendarModel.getCalendarInfos()
await promiseMap(calendarInfos.values(), async ({ groupRoot }) => {
await Promise.all([
this.entityClient.loadAll(CalendarEventTypeRef, groupRoot.longEvents, CUSTOM_MIN_ID).then(() => progressMonitor.workDone(1)),
this.entityClient.loadAll(CalendarEventTypeRef, groupRoot.shortEvents, CUSTOM_MIN_ID).then(() => progressMonitor.workDone(1)),
])
})
progressMonitor.completed()
}
async onPartialLoginSuccess(event: LoggedInEvent): Promise<{ asyncAction: Promise<void> }> {
if (event.sessionType === SessionType.Persistent && this.offlineStorageSettings != null) {
await this.offlineStorageSettings.init()
// Clear the excluded data (i.e. trash and spam lists, old data) in the offline storage.
await this.cacheStorage.clearExcludedData(this.offlineStorageSettings.getTimeRange())
}
return { asyncAction: Promise.resolve() }
}
}