[tutadb] TutanotaV99 - Tutanota Model Cleanup

* Rename MailSet to MailFolder
* Change cardinality of multiple type to One

Co-authored-by: kib <kib@tutao.de>
This commit is contained in:
sug 2025-12-05 15:57:29 +01:00
parent 86f278ebc9
commit db154662ff
61 changed files with 2011 additions and 2100 deletions

View file

@ -4,7 +4,7 @@ use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tutasdk::bindings::test_file_client::TestFileClient;
use tutasdk::entities::generated::tutanota::MailFolder;
use tutasdk::entities::generated::tutanota::MailSet;
use tutasdk::folder_system::MailSetKind;
use tutasdk::net::native_rest_client::NativeRestClient;
use tutasdk::{LoggedInSdk, Sdk};
@ -105,10 +105,7 @@ pub async fn init_file_importer(source_paths: Vec<&str>) -> Importer {
.unwrap()
}
async fn get_test_import_folder_id(
logged_in_sdk: &Arc<LoggedInSdk>,
kind: MailSetKind,
) -> MailFolder {
async fn get_test_import_folder_id(logged_in_sdk: &Arc<LoggedInSdk>, kind: MailSetKind) -> MailSet {
let mail_facade = logged_in_sdk.mail_facade();
let mailbox = mail_facade.load_user_mailbox().await.unwrap();
let folders = mail_facade

View file

@ -8,7 +8,7 @@ import {
ContactCustomDate,
ContactRelationship,
ContactSocialId,
MailFolder,
MailSet,
UserSettingsGroupRoot,
} from "../entities/tutanota/TypeRefs.js"
import { isApp, isElectronClient, isIOSApp } from "./Env"
@ -29,17 +29,17 @@ export const REQUEST_SIZE_LIMIT_MAP: Map<string, number> = new Map([
export const SYSTEM_GROUP_MAIL_ADDRESS = "system@tutanota.de"
export const getMailFolderType = (folder: MailFolder): MailSetKind => downcast(folder.folderType)
export const getMailFolderType = (folder: MailSet): MailSetKind => downcast(folder.folderType)
export function isFolder(folder: MailFolder): boolean {
export function isFolder(folder: MailSet): boolean {
return folder.folderType !== MailSetKind.ALL && folder.folderType !== MailSetKind.LABEL && folder.folderType !== MailSetKind.Imported
}
export function isNestableMailSet(mailSet: MailFolder): boolean {
export function isNestableMailSet(mailSet: MailSet): boolean {
return mailSet.folderType === MailSetKind.CUSTOM
}
export function isLabel(folder: MailFolder): boolean {
export function isLabel(folder: MailSet): boolean {
return folder.folderType === MailSetKind.LABEL
}
@ -121,7 +121,7 @@ export enum MailSetKind {
export const SYSTEM_FOLDERS = [MailSetKind.INBOX, MailSetKind.SENT, MailSetKind.TRASH, MailSetKind.ARCHIVE, MailSetKind.SPAM, MailSetKind.DRAFT] as const
export type SystemFolderType = (typeof SYSTEM_FOLDERS)[number]
export function getMailSetKind(folder: MailFolder): MailSetKind {
export function getMailSetKind(folder: MailSet): MailSetKind {
return folder.folderType as MailSetKind
}

View file

@ -1,20 +1,20 @@
import { groupBy, partition } from "@tutao/tutanota-utils"
import { Mail, MailFolder } from "../../entities/tutanota/TypeRefs.js"
import { Mail, MailSet } from "../../entities/tutanota/TypeRefs.js"
import { isFolder, MailSetKind, SystemFolderType } from "../TutanotaConstants.js"
import { elementIdPart, getElementId, isSameId } from "../utils/EntityUtils.js"
export interface IndentedFolder {
level: number
folder: MailFolder
folder: MailSet
}
/** Accessor for the folder trees. */
export class FolderSystem {
readonly systemSubtrees: ReadonlyArray<FolderSubtree>
readonly customSubtrees: ReadonlyArray<FolderSubtree>
readonly importedMailSet: Readonly<MailFolder | null>
readonly importedMailSet: Readonly<MailSet | null>
constructor(mailSets: readonly MailFolder[]) {
constructor(mailSets: readonly MailSet[]) {
const [folders, nonFolders] = partition(mailSets, (f) => isFolder(f))
const folderByParent = groupBy(folders, (folder) => (folder.parentFolder ? elementIdPart(folder.parentFolder) : null))
const topLevelFolders = folders.filter((f) => f.parentFolder == null)
@ -26,21 +26,21 @@ export class FolderSystem {
this.customSubtrees = customFolders.sort(compareCustom).map((f) => this.makeSubtree(folderByParent, f, compareCustom))
}
getIndentedList(excludeFolder: MailFolder | null = null): IndentedFolder[] {
getIndentedList(excludeFolder: MailSet | null = null): IndentedFolder[] {
return [...this.getIndentedFolderList(this.systemSubtrees, excludeFolder), ...this.getIndentedFolderList(this.customSubtrees, excludeFolder)]
}
/** Search for a specific folder type. Some mailboxes might not have some system folders! */
getSystemFolderByType(type: SystemFolderType): MailFolder | null {
getSystemFolderByType(type: SystemFolderType): MailSet | null {
return this.systemSubtrees.find((f) => f.folder.folderType === type)?.folder ?? null
}
getFolderById(folderId: Id): MailFolder | null {
getFolderById(folderId: Id): MailSet | null {
const subtree = this.getFolderByIdInSubtrees(this.systemSubtrees, folderId) ?? this.getFolderByIdInSubtrees(this.customSubtrees, folderId)
return subtree?.folder ?? null
}
getFolderByMail(mail: Mail): MailFolder | null {
getFolderByMail(mail: Mail): MailSet | null {
const sets = mail.sets
for (const setId of sets) {
const folder = this.getFolderById(elementIdPart(setId))
@ -55,7 +55,7 @@ export class FolderSystem {
* Returns the children of a parent (applies only to custom folders)
* if no parent is given, the top level custom folders are returned
*/
getCustomFoldersOfParent(parent: IdTuple | null): MailFolder[] {
getCustomFoldersOfParent(parent: IdTuple | null): MailSet[] {
if (parent) {
const parentFolder = this.getFolderByIdInSubtrees([...this.customSubtrees, ...this.systemSubtrees], elementIdPart(parent))
return parentFolder ? parentFolder.children.map((child) => child.folder) : []
@ -74,12 +74,12 @@ export class FolderSystem {
}
/** returns all parents of the folder, including the folder itself */
getPathToFolder(folderId: IdTuple): MailFolder[] {
getPathToFolder(folderId: IdTuple): MailSet[] {
return this.getPathToFolderInSubtrees(this.systemSubtrees, folderId) ?? this.getPathToFolderInSubtrees(this.customSubtrees, folderId) ?? []
}
checkFolderForAncestor(folder: MailFolder, potentialAncestorId: IdTuple): boolean {
let currentFolderPointer: MailFolder | null = folder
checkFolderForAncestor(folder: MailSet, potentialAncestorId: IdTuple): boolean {
let currentFolderPointer: MailSet | null = folder
while (true) {
if (currentFolderPointer?.parentFolder == null) {
return false
@ -90,7 +90,7 @@ export class FolderSystem {
}
}
private getIndentedFolderList(subtrees: ReadonlyArray<FolderSubtree>, excludeFolder: MailFolder | null = null, currentLevel: number = 0): IndentedFolder[] {
private getIndentedFolderList(subtrees: ReadonlyArray<FolderSubtree>, excludeFolder: MailSet | null = null, currentLevel: number = 0): IndentedFolder[] {
const plainList: IndentedFolder[] = []
for (const subtree of subtrees) {
if (!excludeFolder || !isSameId(subtree.folder._id, excludeFolder._id)) {
@ -126,7 +126,7 @@ export class FolderSystem {
return null
}
private getPathToFolderInSubtrees(systems: readonly FolderSubtree[], folderId: IdTuple): MailFolder[] | null {
private getPathToFolderInSubtrees(systems: readonly FolderSubtree[], folderId: IdTuple): MailSet[] | null {
for (const system of systems) {
if (isSameId(system.folder._id, folderId)) {
return [system.folder]
@ -139,7 +139,7 @@ export class FolderSystem {
return null
}
private makeSubtree(folderByParent: Map<Id | null, readonly MailFolder[]>, parent: MailFolder, comparator: FolderComparator): FolderSubtree {
private makeSubtree(folderByParent: Map<Id | null, readonly MailSet[]>, parent: MailSet, comparator: FolderComparator): FolderSubtree {
const childrenFolders = folderByParent.get(getElementId(parent))
if (childrenFolders) {
const childSystems = childrenFolders
@ -153,9 +153,9 @@ export class FolderSystem {
}
}
type FolderComparator = (folder1: MailFolder, folder2: MailFolder) => number
type FolderComparator = (folder1: MailSet, folder2: MailSet) => number
function compareCustom(folder1: MailFolder, folder2: MailFolder): number {
function compareCustom(folder1: MailSet, folder2: MailSet): number {
return folder1.name.localeCompare(folder2.name)
}
@ -171,7 +171,7 @@ const folderTypeToOrder: Record<SystemMailFolderTypes, number> = {
[MailSetKind.ALL]: 7,
}
function compareSystem(folder1: MailFolder, folder2: MailFolder): number {
function compareSystem(folder1: MailSet, folder2: MailSet): number {
const order1 = folderTypeToOrder[folder1.folderType as SystemMailFolderTypes] ?? 7
const order2 = folderTypeToOrder[folder2.folderType as SystemMailFolderTypes] ?? 7
return order1 - order2
@ -182,6 +182,6 @@ function compareSystem(folder1: MailFolder, folder2: MailFolder): number {
* the top folders are the toplevel folders in with their respective subfolders.
*/
export interface FolderSubtree {
readonly folder: MailFolder
readonly folder: MailSet
readonly children: readonly FolderSubtree[]
}

View file

@ -1,5 +1,5 @@
const modelInfo = {
version: 36,
version: 37,
}
export default modelInfo

View file

@ -9,7 +9,7 @@ export const typeModels = {
"12": {
"name": "ReadCounterData",
"app": "monitor",
"version": 36,
"version": 37,
"since": 1,
"type": "DATA_TRANSFER_TYPE",
"id": 12,
@ -56,7 +56,7 @@ export const typeModels = {
"16": {
"name": "ReadCounterReturn",
"app": "monitor",
"version": 36,
"version": 37,
"since": 1,
"type": "DATA_TRANSFER_TYPE",
"id": 16,
@ -97,7 +97,7 @@ export const typeModels = {
"49": {
"name": "WriteCounterData",
"app": "monitor",
"version": 36,
"version": 37,
"since": 4,
"type": "DATA_TRANSFER_TYPE",
"id": 49,
@ -152,7 +152,7 @@ export const typeModels = {
"221": {
"name": "ApprovalMail",
"app": "monitor",
"version": 36,
"version": 37,
"since": 14,
"type": "LIST_ELEMENT_TYPE",
"id": 221,
@ -233,7 +233,7 @@ export const typeModels = {
"300": {
"name": "CounterValue",
"app": "monitor",
"version": 36,
"version": 37,
"since": 22,
"type": "AGGREGATED_TYPE",
"id": 300,
@ -272,7 +272,7 @@ export const typeModels = {
"305": {
"name": "ErrorReportFile",
"app": "monitor",
"version": 36,
"version": 37,
"since": 23,
"type": "AGGREGATED_TYPE",
"id": 305,
@ -311,7 +311,7 @@ export const typeModels = {
"316": {
"name": "ErrorReportData",
"app": "monitor",
"version": 36,
"version": 37,
"since": 23,
"type": "AGGREGATED_TYPE",
"id": 316,
@ -406,7 +406,7 @@ export const typeModels = {
"335": {
"name": "ReportErrorIn",
"app": "monitor",
"version": 36,
"version": 37,
"since": 23,
"type": "DATA_TRANSFER_TYPE",
"id": 335,

View file

@ -1,5 +1,5 @@
const modelInfo = {
version: 98,
version: 99,
}
export default modelInfo

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
import type { CryptoFacade } from "../../crypto/CryptoFacade.js"
import {
ApplyLabelService,
ClientClassifierResultService,
DraftService,
ExternalUserService,
ListUnsubscribeService,
@ -34,7 +33,6 @@ import {
MAX_NBR_OF_MAILS_SYNC_OPERATION,
OperationType,
PhishingMarkerStatus,
ProcessingState,
PublicKeyIdentifierType,
ReportedMailFieldType,
SimpleMoveMailTarget,
@ -44,7 +42,6 @@ import {
Contact,
createApplyLabelServicePostIn,
createAttachmentKeyData,
createClientClassifierResultPostIn,
createCreateExternalUserGroupData,
createCreateMailFolderData,
createDeleteMailData,
@ -84,7 +81,7 @@ import {
MailDetails,
MailDetailsBlobTypeRef,
MailDetailsDraftTypeRef,
MailFolder,
MailSet,
MailTypeRef,
MovedMails,
PopulateClientSpamTrainingDatum,
@ -245,7 +242,7 @@ export class MailFacade {
* @param folder to be updated
* @param newName - if this is the same as the folder's current name, nothing is done
*/
async updateMailFolderName(folder: MailFolder, newName: string): Promise<void> {
async updateMailFolderName(folder: MailSet, newName: string): Promise<void> {
if (newName !== folder.name) {
folder.name = newName
await this.entityClient.update(folder)
@ -264,7 +261,7 @@ export class MailFacade {
* @param folder to be updated
* @param newParent - if this is the same as the folder's current parent, nothing is done
*/
async updateMailFolderParent(folder: MailFolder, newParent: IdTuple | null): Promise<void> {
async updateMailFolderParent(folder: MailSet, newParent: IdTuple | null): Promise<void> {
const isOwnParent = isSameId(folder._id, newParent)
const isDifferentParent = folder.parentFolder != null && newParent != null && !isSameId(folder.parentFolder, newParent)
const isNewParent = folder.parentFolder == null && newParent != null
@ -750,7 +747,7 @@ export class MailFacade {
await this.serviceExecutor.delete(MailFolderService, deleteMailFolderData, { sessionKey: "dummy" as any })
}
async fixupCounterForFolder(groupId: Id, folder: MailFolder, unreadMails: number): Promise<void> {
async fixupCounterForFolder(groupId: Id, folder: MailSet, unreadMails: number): Promise<void> {
const counterId = getElementId(folder)
const data = createWriteCounterData({
counterType: CounterType.UnreadMails,
@ -1132,7 +1129,7 @@ export class MailFacade {
}
/**
* Create a label (aka MailSet aka {@link MailFolder} of kind {@link MailSetKind.LABEL}) for the group {@param mailGroupId}.
* Create a label (aka MailSet aka {@link MailSet} of kind {@link MailSetKind.LABEL}) for the group {@param mailGroupId}.
*/
async createLabel(mailGroupId: Id, labelData: { name: string; color: string }) {
const mailGroupKey = await this.keyLoaderFacade.getCurrentSymGroupKey(mailGroupId)
@ -1162,7 +1159,7 @@ export class MailFacade {
* @param name possible new name for label
* @param color possible new color for label
*/
async updateLabel(label: MailFolder, name: string, color: string) {
async updateLabel(label: MailSet, name: string, color: string) {
if (name !== label.name || color !== label.color) {
label.name = name
label.color = color
@ -1170,7 +1167,7 @@ export class MailFacade {
}
}
async deleteLabel(label: MailFolder) {
async deleteLabel(label: MailSet) {
await this.serviceExecutor.delete(
ManageLabelService,
createManageLabelServiceDeleteIn({
@ -1179,7 +1176,7 @@ export class MailFacade {
)
}
async applyLabels(mailIds: IdTuple[], addedLabels: readonly MailFolder[], removedLabels: readonly MailFolder[]) {
async applyLabels(mailIds: IdTuple[], addedLabels: readonly MailSet[], removedLabels: readonly MailSet[]) {
const postIn = createApplyLabelServicePostIn({
mails: mailIds,
addedLabels: addedLabels.map((label) => label._id),
@ -1213,22 +1210,6 @@ export class MailFacade {
* @param mails mail ids to mark as unread
* @param processingState
*/
async updateMailPredictionState(mails: readonly IdTuple[], processingState: ProcessingState) {
const isPredictionMade = processingState === ProcessingState.INBOX_RULE_PROCESSED_AND_SPAM_PREDICTION_MADE
await promiseMap(
splitInChunks(MAX_NBR_OF_MAILS_SYNC_OPERATION, mails),
async (mails) =>
this.serviceExecutor.post(
ClientClassifierResultService,
createClientClassifierResultPostIn({
mails,
isPredictionMade: isPredictionMade,
}),
),
{ concurrency: 5 },
)
}
private async encryptUnencryptedProcessInboxData(
mailGroupId: Id,
unencryptedProcessInboxData: readonly UnencryptedProcessInboxDatum[],

View file

@ -5,7 +5,7 @@ import { ButtonType } from "../../gui/base/Button.js"
import m from "mithril"
import { DropDownSelector, DropDownSelectorAttrs } from "../../gui/base/DropDownSelector.js"
import { BootIcons } from "../../gui/base/icons/BootIcons.js"
import { MailFolder } from "../../api/entities/tutanota/TypeRefs"
import { MailSet } from "../../api/entities/tutanota/TypeRefs"
import { IndentedFolder } from "../../api/common/mail/FolderSystem"
import { repeat } from "@tutao/tutanota-utils"
@ -14,7 +14,7 @@ import { repeat } from "@tutao/tutanota-utils"
* @param indentedFolders List of user's folders
* @param okAction
*/
export function folderSelectionDialog(indentedFolders: IndentedFolder[], okAction: (dialog: Dialog, selectedMailFolder: MailFolder) => unknown) {
export function folderSelectionDialog(indentedFolders: IndentedFolder[], okAction: (dialog: Dialog, selectedMailFolder: MailSet) => unknown) {
let selectedIndentedFolder = indentedFolders[0]
const dialog = new Dialog(DialogType.EditSmall, {
@ -54,7 +54,7 @@ export function folderSelectionDialog(indentedFolders: IndentedFolder[], okActio
selectedValue: selectedIndentedFolder.folder,
selectionChangedHandler: (v) => (selectedIndentedFolder.folder = v),
icon: BootIcons.Expand,
} satisfies DropDownSelectorAttrs<MailFolder>),
} satisfies DropDownSelectorAttrs<MailSet>),
]),
],
}).show()

View file

@ -1,5 +1,5 @@
import { getApiBaseUrl } from "../../../common/api/common/Env"
import { ImportMailState, ImportMailStateTypeRef, MailBox, MailFolder, MailFolderTypeRef } from "../../../common/api/entities/tutanota/TypeRefs"
import { ImportMailState, ImportMailStateTypeRef, MailBox, MailSet, MailSetTypeRef } from "../../../common/api/entities/tutanota/TypeRefs"
import { assertNotNull, first, isEmpty } from "@tutao/tutanota-utils"
import { NativeMailImportFacade } from "../../../common/native/common/generatedipc/NativeMailImportFacade"
import { CredentialsProvider } from "../../../common/misc/credentials/CredentialsProvider"
@ -39,7 +39,7 @@ export class MailImporter {
private finalisedImportStates: Map<Id, ImportMailState> = new Map()
private activeImport: ActiveImport | null = null
public foldersForMailbox: FolderSystem | undefined
public selectedTargetFolder: MailFolder | null = null
public selectedTargetFolder: MailSet | null = null
constructor(
private readonly domainConfigProvider: DomainConfigProvider,
@ -122,7 +122,7 @@ export class MailImporter {
uiStatus: UiImportStatus.Paused,
progressMonitor,
}
this.selectedTargetFolder = await this.entityClient.load(MailFolderTypeRef, importMailState.targetFolder)
this.selectedTargetFolder = await this.entityClient.load(MailSetTypeRef, importMailState.targetFolder)
}
}
}

View file

@ -1,6 +1,6 @@
import { applyInboxRulesAndSpamPrediction, LoadedMail, MailSetListModel, resolveMailSetEntries } from "./MailSetListModel"
import { ListLoadingState, ListState } from "../../../common/gui/base/List"
import { Mail, MailFolder, MailFolderTypeRef, MailSetEntry, MailSetEntryTypeRef, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs"
import { Mail, MailSet, MailSetTypeRef, MailSetEntry, MailSetEntryTypeRef, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs"
import { EntityUpdateData, isUpdateForTypeRef } from "../../../common/api/common/utils/EntityUpdateUtils"
import { ListFilter, ListModel } from "../../../common/misc/ListModel"
import Stream from "mithril/stream"
@ -64,7 +64,7 @@ export class ConversationListModel implements MailSetListModel {
private olderDisplayedSelectedMailOverride: Id | null = null
constructor(
private readonly mailSet: MailFolder,
private readonly mailSet: MailSet,
private readonly conversationPrefProvider: ConversationPrefProvider,
private readonly entityClient: EntityClient,
private readonly mailModel: MailModel,
@ -108,7 +108,7 @@ export class ConversationListModel implements MailSetListModel {
this.listModel.enterMultiselect()
}
getLabelsForMail(mail: Mail): ReadonlyArray<MailFolder> {
getLabelsForMail(mail: Mail): ReadonlyArray<MailSet> {
return this._getLoadedMail(getElementId(mail))?.labels ?? []
}
@ -122,7 +122,7 @@ export class ConversationListModel implements MailSetListModel {
)
async handleEntityUpdate(update: EntityUpdateData) {
if (isUpdateForTypeRef(MailFolderTypeRef, update)) {
if (isUpdateForTypeRef(MailSetTypeRef, update)) {
if (update.operation === OperationType.UPDATE) {
this.handleMailFolderUpdate([update.instanceListId, update.instanceId])
}

View file

@ -1,7 +1,7 @@
import { InboxRule, Mail, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { InboxRule, Mail, MailSet } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { InboxRuleType, MailSetKind, ProcessingState } from "../../../common/api/common/TutanotaConstants"
import { isDomainName, isRegularExpression } from "../../../common/misc/FormatValidator"
import { assertNotNull, asyncFind, Nullable } from "@tutao/tutanota-utils"
import { asyncFind, Nullable } from "@tutao/tutanota-utils"
import { lang } from "../../../common/misc/LanguageViewModel"
import type { MailboxDetail } from "../../../common/mailFunctionality/MailboxModel.js"
import type { SelectorItemList } from "../../../common/gui/base/DropDownSelector.js"
@ -64,19 +64,13 @@ export class InboxRuleHandler {
async findAndApplyMatchingRule(
mailboxDetail: MailboxDetail,
mail: Readonly<Mail>,
): Promise<Nullable<{ targetFolder: MailFolder; processInboxDatum: UnencryptedProcessInboxDatum }>> {
): Promise<Nullable<{ targetFolder: MailSet; processInboxDatum: UnencryptedProcessInboxDatum }>> {
const shouldApply =
(mail.processingState === ProcessingState.INBOX_RULE_NOT_PROCESSED ||
mail.processingState === ProcessingState.INBOX_RULE_NOT_PROCESSED_AND_DO_NOT_RUN_SPAM_PREDICTION) &&
mail.processNeeded
if (
mail._errors ||
!shouldApply ||
!(await isLandingFolder(this.mailModel, mailboxDetail, mail)) ||
!this.logins.getUserController().isPaidAccount() ||
mailboxDetail.mailbox.folders == null
) {
if (mail._errors || !shouldApply || !(await isLandingFolder(this.mailModel, mailboxDetail, mail)) || !this.logins.getUserController().isPaidAccount()) {
return null
}
@ -194,7 +188,7 @@ function _checkEmailAddresses(mailAddresses: string[], inboxRule: InboxRule): bo
}
async function isLandingFolder(mailModel: MailModel, mailboxDetail: MailboxDetail, mail: Mail): Promise<boolean> {
const folders = await mailModel.getMailboxFoldersForId(assertNotNull(mailboxDetail.mailbox.folders)._id)
const folders = await mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
const mailFolder = folders.getFolderByMail(mail)
return mailFolder?.folderType === MailSetKind.INBOX || mailFolder?.folderType === MailSetKind.SPAM
}

View file

@ -1,11 +1,11 @@
//@bundleInto:common
import { Mail, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { Mail, MailSet } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { MailModel } from "./MailModel.js"
import { FolderSystem } from "../../../common/api/common/mail/FolderSystem.js"
import { MailSetKind, SystemFolderType } from "../../../common/api/common/TutanotaConstants.js"
export function isSubfolderOfType(system: FolderSystem, folder: MailFolder, type: SystemFolderType): boolean {
export function isSubfolderOfType(system: FolderSystem, folder: MailSet, type: SystemFolderType): boolean {
const systemFolder = system.getSystemFolderByType(type)
return systemFolder != null && system.checkFolderForAncestor(folder, systemFolder._id)
}
@ -34,14 +34,14 @@ export async function isMailInSpam(mail: Mail, mailModel: MailModel): Promise<bo
}
}
export function isSpamFolder(system: FolderSystem, folder: MailFolder): boolean {
export function isSpamFolder(system: FolderSystem, folder: MailSet): boolean {
return folder.folderType === MailSetKind.SPAM || isSubfolderOfType(system, folder, MailSetKind.SPAM)
}
/**
* Returns true if given folder is the {@link MailFolderType.SPAM} or {@link MailFolderType.TRASH} folder, or a descendant of those folders.
*/
export function isSpamOrTrashFolder(system: FolderSystem, folder: MailFolder): boolean {
export function isSpamOrTrashFolder(system: FolderSystem, folder: MailSet): boolean {
// not using isOfTypeOrSubfolderOf because checking the type first is cheaper
return (
folder.folderType === MailSetKind.TRASH ||
@ -51,6 +51,6 @@ export function isSpamOrTrashFolder(system: FolderSystem, folder: MailFolder): b
)
}
export function isOfTypeOrSubfolderOf(system: FolderSystem, folder: MailFolder, type: SystemFolderType): boolean {
export function isOfTypeOrSubfolderOf(system: FolderSystem, folder: MailSet, type: SystemFolderType): boolean {
return folder.folderType === type || isSubfolderOfType(system, folder, type)
}

View file

@ -1,5 +1,5 @@
import { ListFilter, ListModel } from "../../../common/misc/ListModel"
import { Mail, MailFolder, MailFolderTypeRef, MailSetEntry, MailSetEntryTypeRef, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs"
import { Mail, MailSet, MailSetTypeRef, MailSetEntry, MailSetEntryTypeRef, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs"
import {
CUSTOM_MAX_ID,
customIdToUint8array,
@ -38,7 +38,7 @@ export class MailListModel implements MailSetListModel {
private readonly mailMap: Map<Id, LoadedMail> = new Map()
constructor(
private readonly mailSet: MailFolder,
private readonly mailSet: MailSet,
private readonly conversationPrefProvider: ConversationPrefProvider,
private readonly entityClient: EntityClient,
private readonly mailModel: MailModel,
@ -118,7 +118,7 @@ export class MailListModel implements MailSetListModel {
return this.getLoadedMailByMailId(mailElementId)?.mail ?? null
}
getLabelsForMail(mail: Mail): ReadonlyArray<MailFolder> {
getLabelsForMail(mail: Mail): ReadonlyArray<MailSet> {
return this.getLoadedMailByMailInstance(mail)?.labels ?? []
}
@ -150,7 +150,7 @@ export class MailListModel implements MailSetListModel {
)
async handleEntityUpdate(update: EntityUpdateData) {
if (isUpdateForTypeRef(MailFolderTypeRef, update)) {
if (isUpdateForTypeRef(MailSetTypeRef, update)) {
// If a label is modified, we want to update all mails that reference it, which requires linearly iterating
// through all mails. There are more efficient ways we could do this, such as by keeping track of each label
// we've retrieved from the database and just update that, but we want to avoid adding more maps that we

View file

@ -20,8 +20,8 @@ import {
Mail,
MailboxGroupRoot,
MailboxProperties,
MailFolder,
MailFolderTypeRef,
MailSet,
MailSetTypeRef,
MailSetEntryTypeRef,
MailTypeRef,
MovedMails,
@ -60,7 +60,7 @@ import { isWebClient } from "../../../common/api/common/Env"
interface MailboxSets {
folders: FolderSystem
/** a map from element id to the mail set */
labels: ReadonlyMap<Id, MailFolder>
labels: ReadonlyMap<Id, MailSet>
}
export const enum LabelState {
@ -123,7 +123,7 @@ export class MailModel {
for (let detail of mailboxDetails) {
const foldersRef = detail.mailbox.folders
if (foldersRef != null) {
let mailSets: MailFolder[]
let mailSets: MailSet[]
try {
mailSets = await this.loadMailSetsForListId(foldersRef.folders)
} catch (e) {
@ -162,8 +162,8 @@ export class MailModel {
return tempFolders
}
private async loadMailSetsForListId(listId: Id): Promise<MailFolder[]> {
const folders = await this.entityClient.loadAll(MailFolderTypeRef, listId)
private async loadMailSetsForListId(listId: Id): Promise<MailSet[]> {
const folders = await this.entityClient.loadAll(MailSetTypeRef, listId)
return folders.filter((f) => {
// We do not show spam or archive for external users
@ -186,7 +186,7 @@ export class MailModel {
// visibleForTesting
async entityEventsReceived(updates: ReadonlyArray<EntityUpdateData>): Promise<void> {
for (const update of updates) {
if (isUpdateForTypeRef(MailFolderTypeRef, update)) {
if (isUpdateForTypeRef(MailSetTypeRef, update)) {
await this.init()
m.redraw()
} else if (isUpdateForTypeRef(MailTypeRef, update) && update.operation === OperationType.CREATE) {
@ -239,7 +239,7 @@ export class MailModel {
return detail
}
async getMailboxDetailsForMailFolder(mailFolder: MailFolder): Promise<MailboxDetail | null> {
async getMailboxDetailsForMailFolder(mailFolder: MailSet): Promise<MailboxDetail | null> {
const detail = await this.mailboxModel.getMailboxDetailsForMailGroup(assertNotNull(mailFolder._ownerGroup))
if (detail == null) {
console.warn("mailbox detail for mail folder does not exist", mailFolder)
@ -249,7 +249,7 @@ export class MailModel {
async getMailboxFoldersForMail(mail: Mail): Promise<FolderSystem | null> {
const mailboxDetail = await this.getMailboxDetailsForMail(mail)
if (mailboxDetail && mailboxDetail.mailbox.folders) {
if (mailboxDetail) {
const folders = await this.getFolders()
return folders.get(mailboxDetail.mailbox.folders._id)?.folders ?? null
} else {
@ -266,7 +266,7 @@ export class MailModel {
return folderSystem
}
getMailFolderForMail(mail: Mail): MailFolder | null {
getMailFolderForMail(mail: Mail): MailSet | null {
const folderSystem = this.getFolderSystemByGroupId(assertNotNull(mail._ownerGroup))
if (folderSystem == null) return null
@ -277,14 +277,14 @@ export class MailModel {
return this.getMailSetsForGroup(groupId)?.folders ?? null
}
getLabelsByGroupId(groupId: Id): ReadonlyMap<Id, MailFolder> {
getLabelsByGroupId(groupId: Id): ReadonlyMap<Id, MailSet> {
return this.getMailSetsForGroup(groupId)?.labels ?? new Map()
}
/**
* @return all labels that could be applied to the {@param mails} with the state relative to {@param mails}.
*/
getLabelStatesForMails(mails: readonly Mail[]): { label: MailFolder; state: LabelState }[] {
getLabelStatesForMails(mails: readonly Mail[]): { label: MailSet; state: LabelState }[] {
if (mails.length === 0) {
return []
}
@ -304,8 +304,8 @@ export class MailModel {
})
}
getLabelsForMails(mails: readonly Mail[]): ReadonlyMap<Id, ReadonlyArray<MailFolder>> {
const labelsForMails = new Map<Id, MailFolder[]>()
getLabelsForMails(mails: readonly Mail[]): ReadonlyMap<Id, ReadonlyArray<MailSet>> {
const labelsForMails = new Map<Id, MailSet[]>()
for (const mail of mails) {
labelsForMails.set(getElementId(mail), this.getLabelsForMail(mail))
}
@ -316,7 +316,7 @@ export class MailModel {
/**
* @return labels that are currently applied to {@param mail}.
*/
getLabelsForMail(mail: Mail): MailFolder[] {
getLabelsForMail(mail: Mail): MailSet[] {
const groupLabels = this.getLabelsByGroupId(assertNotNull(mail._ownerGroup))
return mail.sets.map((labelId) => groupLabels.get(elementIdPart(labelId))).filter(isNotNull)
}
@ -324,7 +324,7 @@ export class MailModel {
private getMailSetsForGroup(groupId: Id): MailboxSets | null {
const mailboxDetails = this.mailboxModel.mailboxDetails() || []
const detail = mailboxDetails.find((md) => groupId === md.mailGroup._id)
const sets = detail?.mailbox?.folders?._id
const sets = detail?.mailbox?.folders._id
if (sets == null) {
return null
}
@ -349,7 +349,7 @@ export class MailModel {
/**
* Move mails from {@param targetFolder} except those that are in {@param excludeMailSet}.
*/
async moveMails(mails: readonly IdTuple[], targetFolder: MailFolder, moveMode: MoveMode): Promise<MovedMails[]> {
async moveMails(mails: readonly IdTuple[], targetFolder: MailSet, moveMode: MoveMode): Promise<MovedMails[]> {
const folderSystem = this.getFolderSystemByGroupId(assertNotNull(targetFolder._ownerGroup))
if (folderSystem == null) {
return []
@ -372,7 +372,7 @@ export class MailModel {
/**
* Sends the given folder and all its descendants to the spam folder, reporting mails (if applicable) and removes any empty folders
*/
async sendFolderToSpam(folder: MailFolder): Promise<void> {
async sendFolderToSpam(folder: MailSet): Promise<void> {
const mailboxDetail = await this.getMailboxDetailsForMailFolder(folder)
if (mailboxDetail == null) {
return
@ -419,7 +419,7 @@ export class MailModel {
await this.mailFacade.markMails(mails, unread)
}
async applyLabels(mails: readonly IdTuple[], addedLabels: readonly MailFolder[], removedLabels: readonly MailFolder[]): Promise<void> {
async applyLabels(mails: readonly IdTuple[], addedLabels: readonly MailSet[], removedLabels: readonly MailSet[]): Promise<void> {
const groupedByListIds = groupBy(mails, (mailId) => listIdPart(mailId))
for (const [_, groupedMails] of groupedByListIds) {
const mailChunks = splitInChunks(MAX_NBR_OF_MAILS_SYNC_OPERATION, groupedMails)
@ -439,14 +439,14 @@ export class MailModel {
this.mailboxCounters(normalized)
}
_showNotification(folder: MailFolder, mail: Mail) {
_showNotification(folder: MailSet, mail: Mail) {
this.notifications.showNotification(NotificationType.Mail, lang.get("newMails_msg"), {}, (_) => {
m.route.set(`/mail/${getElementId(folder)}/${getElementId(mail)}`)
window.focus()
})
}
getCounterValue(folder: MailFolder): Promise<number | null> {
getCounterValue(folder: MailSet): Promise<number | null> {
return this.getMailboxDetailsForMailFolder(folder)
.then((mailboxDetails) => {
if (mailboxDetails == null) {
@ -477,7 +477,7 @@ export class MailModel {
/**
* Sends the given folder and all its descendants to the trash folder, removes any empty folders
*/
async trashFolderAndSubfolders(folder: MailFolder): Promise<void> {
async trashFolderAndSubfolders(folder: MailSet): Promise<void> {
const mailboxDetail = await this.getMailboxDetailsForMailFolder(folder)
if (mailboxDetail == null) {
return
@ -492,14 +492,14 @@ export class MailModel {
}
}
async setParentForFolder(folder: MailFolder, newParentId: IdTuple) {
async setParentForFolder(folder: MailSet, newParentId: IdTuple) {
return this.mailFacade.updateMailFolderParent(folder, newParentId)
}
/**
* This is called when moving a folder to SPAM or TRASH, which do not allow empty folders (since only folders that contain mail are allowed)
*/
private async removeAllEmpty(folderSystem: FolderSystem, folder: MailFolder): Promise<boolean> {
private async removeAllEmpty(folderSystem: FolderSystem, folder: MailSet): Promise<boolean> {
// sort descendants deepest first so that we can clean them up before checking their ancestors
const descendants = folderSystem.getDescendantFoldersOfParent(folder._id).sort((l, r) => r.level - l.level)
@ -531,11 +531,11 @@ export class MailModel {
}
// Only load one mail, if there is even one we won't remove
private async isEmptyFolder(descendant: MailFolder) {
private async isEmptyFolder(descendant: MailSet) {
return (await this.entityClient.loadRange(MailSetEntryTypeRef, descendant.entries, CUSTOM_MIN_ID, 1, false)).length === 0
}
public async finallyDeleteCustomMailFolder(folder: MailFolder): Promise<void> {
public async finallyDeleteCustomMailFolder(folder: MailSet): Promise<void> {
if (folder.folderType !== MailSetKind.CUSTOM && folder.folderType !== MailSetKind.Imported) {
throw new ProgrammingError("Cannot delete non-custom folder: " + String(folder._id))
}
@ -550,14 +550,14 @@ export class MailModel {
)
}
async fixupCounterForFolder(folder: MailFolder, unreadMails: number) {
async fixupCounterForFolder(folder: MailSet, unreadMails: number) {
const mailboxDetails = await this.getMailboxDetailsForMailFolder(folder)
if (mailboxDetails) {
await this.mailFacade.fixupCounterForFolder(mailboxDetails.mailGroup._id, folder, unreadMails)
}
}
async clearFolder(folder: MailFolder): Promise<void> {
async clearFolder(folder: MailSet): Promise<void> {
await this.mailFacade.clearFolder(folder._id)
}
@ -573,21 +573,21 @@ export class MailModel {
}
/**
* Create a label (aka MailSet aka {@link MailFolder} of kind {@link MailSetKind.LABEL}) for the group {@param mailGroupId}.
* Create a label (aka MailSet aka {@link MailSet} of kind {@link MailSetKind.LABEL}) for the group {@param mailGroupId}.
*/
async createLabel(mailGroupId: Id, labelData: { name: string; color: string }) {
await this.mailFacade.createLabel(mailGroupId, labelData)
}
async updateLabel(label: MailFolder, newData: { name: string; color: string }) {
async updateLabel(label: MailSet, newData: { name: string; color: string }) {
await this.mailFacade.updateLabel(label, newData.name, newData.color)
}
async deleteLabel(label: MailFolder) {
async deleteLabel(label: MailSet) {
await this.mailFacade.deleteLabel(label)
}
async getMailSetById(folderElementId: Id): Promise<MailFolder | null> {
async getMailSetById(folderElementId: Id): Promise<MailSet | null> {
const folderStructures = await this.loadMailSets()
for (const folders of folderStructures.values()) {
const folder = folders.folders.getFolderById(folderElementId)
@ -603,7 +603,7 @@ export class MailModel {
return null
}
getImportedMailSets(): Array<MailFolder> {
getImportedMailSets(): Array<MailSet> {
return [...this.mailSets.values()].filter((f) => f.folders.importedMailSet).map((f) => f.folders.importedMailSet!)
}

View file

@ -1,4 +1,4 @@
import { Mail, MailFolder, MailSetEntry } from "../../../common/api/entities/tutanota/TypeRefs"
import { Mail, MailSet, MailSetEntry } from "../../../common/api/entities/tutanota/TypeRefs"
import { ListFilter } from "../../../common/misc/ListModel"
import { ListLoadingState, ListState } from "../../../common/gui/base/List"
import { EntityUpdateData } from "../../../common/api/common/utils/EntityUpdateUtils"
@ -183,7 +183,7 @@ export interface MailSetListModel {
* Get all labels for the mail.
* @param mail
*/
getLabelsForMail(mail: Mail): ReadonlyArray<MailFolder>
getLabelsForMail(mail: Mail): ReadonlyArray<MailSet>
/**
* Load the entire list.
@ -215,7 +215,7 @@ export interface MailSetListModel {
export interface LoadedMail {
readonly mail: Mail
readonly mailSetEntryId: IdTuple
readonly labels: ReadonlyArray<MailFolder>
readonly labels: ReadonlyArray<MailSet>
}
/**
@ -247,7 +247,7 @@ export async function resolveMailSetEntries(
}
// Resolve labels
const labels: MailFolder[] = mailModel.getLabelsForMail(mail)
const labels: MailSet[] = mailModel.getLabelsForMail(mail)
loadedMails.push({ mailSetEntryId: mailSetEntry._id, mail, labels })
}
@ -278,7 +278,7 @@ export async function provideAllMails(ids: IdTuple[], mailProvider: (listId: Id,
*/
export async function applyInboxRulesAndSpamPrediction(
entries: LoadedMail[],
sourceFolder: MailFolder,
sourceFolder: MailSet,
mailModel: MailModel,
processInboxHandler: ProcessInboxHandler,
sendServerRequest: boolean,

View file

@ -1,12 +1,12 @@
import { FolderSystem, IndentedFolder } from "../../../common/api/common/mail/FolderSystem.js"
import { Header, InboxRule, Mail, MailDetails, MailFolder, TutanotaProperties } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { Header, InboxRule, Mail, MailDetails, MailSet, TutanotaProperties } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { assertNotNull, first } from "@tutao/tutanota-utils"
import { MailModel } from "./MailModel.js"
import { lang } from "../../../common/misc/LanguageViewModel.js"
import { MailSetKind, ReplyType, SYSTEM_FOLDERS, SystemFolderType } from "../../../common/api/common/TutanotaConstants.js"
import { isSameId, sortCompareByReverseId } from "../../../common/api/common/utils/EntityUtils"
export type FolderInfo = { level: number; folder: MailFolder }
export type FolderInfo = { level: number; folder: MailSet }
export const enum MoveService {
RegularMove = "RegularMove",
@ -27,7 +27,7 @@ export type MoveTargets = RegularMoveTargets | SimpleMoveTargets
export const MAX_FOLDER_INDENT_LEVEL = 10
export function getFolderName(folder: MailFolder): string {
export function getFolderName(folder: MailSet): string {
switch (folder.folderType) {
case MailSetKind.CUSTOM:
return folder.name
@ -78,7 +78,7 @@ export async function getMoveTargetFolderSystems(foldersModel: MailModel, mails:
if (firstMail == null) return regularMoveTargets([])
const mailboxDetails = await foldersModel.getMailboxDetailsForMail(firstMail)
if (mailboxDetails == null || mailboxDetails.mailbox.folders == null) {
if (mailboxDetails == null) {
return regularMoveTargets([])
}
@ -116,9 +116,9 @@ export async function getMoveTargetFolderSystems(foldersModel: MailModel, mails:
}
}
export async function getMoveTargetFolderSystemsForMailsInFolder(foldersModel: MailModel, currentFolder: MailFolder): Promise<Array<FolderInfo>> {
export async function getMoveTargetFolderSystemsForMailsInFolder(foldersModel: MailModel, currentFolder: MailSet): Promise<Array<FolderInfo>> {
const mailboxDetails = await foldersModel.getMailboxDetailsForMailFolder(currentFolder)
if (mailboxDetails == null || mailboxDetails.mailbox.folders == null) {
if (mailboxDetails == null) {
return []
}
@ -138,11 +138,11 @@ export async function getMoveTargetFolderSystemsForMailsInFolder(foldersModel: M
*
* Use with caution.
*/
export function assertSystemFolderOfType(system: FolderSystem, type: SystemFolderType): MailFolder {
export function assertSystemFolderOfType(system: FolderSystem, type: SystemFolderType): MailSet {
return assertNotNull(system.getSystemFolderByType(type), "System folder of type does not exist!")
}
export function getPathToFolderString(folderSystem: FolderSystem, folder: MailFolder, omitLast = false) {
export function getPathToFolderString(folderSystem: FolderSystem, folder: MailSet, omitLast = false) {
const folderPath = folderSystem.getPathToFolder(folder._id)
if (omitLast) {
folderPath.pop()

View file

@ -1,6 +1,6 @@
import { SpamClassificationHandler } from "./SpamClassificationHandler"
import { InboxRuleHandler } from "./InboxRuleHandler"
import { Mail, MailFolder, ProcessInboxDatum } from "../../../common/api/entities/tutanota/TypeRefs"
import { Mail, MailSet, ProcessInboxDatum } from "../../../common/api/entities/tutanota/TypeRefs"
import { FeatureType, MailSetKind } from "../../../common/api/common/TutanotaConstants"
import { assertNotNull, debounce, isEmpty, Nullable } from "@tutao/tutanota-utils"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade"
@ -47,11 +47,11 @@ export class ProcessInboxHandler {
public async handleIncomingMail(
mail: Mail,
sourceFolder: MailFolder,
sourceFolder: MailSet,
mailboxDetail: MailboxDetail,
folderSystem: FolderSystem,
sendServerRequest: boolean,
): Promise<MailFolder> {
): Promise<MailSet> {
await this.logins.loadCustomizations()
const isSpamClassificationFeatureEnabled = this.logins.isEnabled(FeatureType.SpamClientClassification)
if (!mail.processNeeded) {
@ -61,7 +61,7 @@ export class ProcessInboxHandler {
const mailDetails = await this.mailFacade.loadMailDetailsBlob(mail)
let finalProcessInboxDatum: Nullable<UnencryptedProcessInboxDatum> = null
let moveToFolder: MailFolder = sourceFolder
let moveToFolder: MailSet = sourceFolder
if (sourceFolder.folderType === MailSetKind.INBOX || sourceFolder.folderType === MailSetKind.SPAM) {
const result = await this.inboxRuleHandler()?.findAndApplyMatchingRule(mailboxDetail, mail)

View file

@ -1,4 +1,4 @@
import { Mail, MailDetails, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs"
import { Mail, MailDetails, MailSet } from "../../../common/api/entities/tutanota/TypeRefs"
import { MailPhishingStatus, MailSetKind } from "../../../common/api/common/TutanotaConstants"
import { SpamClassifier } from "../../workerUtils/spamClassification/SpamClassifier"
import { assertNotNull } from "@tutao/tutanota-utils"
@ -17,9 +17,9 @@ export class SpamClassificationHandler {
public async predictSpamForNewMail(
mail: Mail,
mailDetails: MailDetails,
sourceFolder: MailFolder,
sourceFolder: MailSet,
folderSystem: FolderSystem,
): Promise<{ targetFolder: MailFolder; processInboxDatum: UnencryptedProcessInboxDatum }> {
): Promise<{ targetFolder: MailSet; processInboxDatum: UnencryptedProcessInboxDatum }> {
const spamMailDatum = createSpamMailDatum(mail, mailDetails)
const vectorizedMail = await this.spamClassifier.vectorize(spamMailDatum)

View file

@ -303,7 +303,7 @@ export class ConversationViewModel {
private async isInTrash(mail: Mail) {
const mailboxDetail = await this.mailModel.getMailboxDetailsForMail(mail)
const mailFolder = this.mailModel.getMailFolderForMail(mail)
if (mailFolder == null || mailboxDetail == null || mailboxDetail.mailbox.folders == null) {
if (mailFolder == null || mailboxDetail == null) {
return
}
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)

View file

@ -1,4 +1,4 @@
import { Mail, MailFolder, MailSetEntryTypeRef, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { Mail, MailSet, MailSetEntryTypeRef, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { DropDownSelector, SelectorItemList } from "../../../common/gui/base/DropDownSelector.js"
import m from "mithril"
import { TextField } from "../../../common/gui/base/TextField.js"
@ -11,7 +11,7 @@ import { MailReportType, MailSetKind } from "../../../common/api/common/Tutanota
import { elementIdPart, isSameId, listIdPart } from "../../../common/api/common/utils/EntityUtils.js"
import { reportMailsAutomatically } from "./MailReportDialog.js"
import { isOfflineError } from "../../../common/api/common/utils/ErrorUtils.js"
import { assertNotNull, groupByAndMap } from "@tutao/tutanota-utils"
import { groupByAndMap } from "@tutao/tutanota-utils"
import { mailLocator } from "../../mailLocator.js"
import type { FolderSystem, IndentedFolder } from "../../../common/api/common/mail/FolderSystem.js"
import { getFolderName, getIndentedFolderNameForDropdown, getPathToFolderString } from "../model/MailUtils.js"
@ -21,12 +21,12 @@ import { isSpamOrTrashFolder } from "../model/MailChecks.js"
* Dialog for Edit and Add folder are the same.
* @param editedFolder if this is null, a folder is being added, otherwise a folder is being edited
*/
export async function showEditFolderDialog(mailBoxDetail: MailboxDetail, editedFolder: MailFolder | null = null, parentFolder: MailFolder | null = null) {
export async function showEditFolderDialog(mailBoxDetail: MailboxDetail, editedFolder: MailSet | null = null, parentFolder: MailSet | null = null) {
const noParentFolderOption = lang.get("comboBoxSelectionNone_msg")
const mailGroupId = mailBoxDetail.mailGroup._id
const folders = await mailLocator.mailModel.getMailboxFoldersForId(assertNotNull(mailBoxDetail.mailbox.folders)._id)
const folders = await mailLocator.mailModel.getMailboxFoldersForId(mailBoxDetail.mailbox.folders._id)
let folderNameValue = editedFolder?.name ?? ""
let targetFolders: SelectorItemList<MailFolder | null> = folders
let targetFolders: SelectorItemList<MailSet | null> = folders
.getIndentedList(editedFolder)
// filter: SPAM and TRASH and descendants are only shown if editing (folders can only be moved there, not created there)
.filter((folderInfo: IndentedFolder) => !(editedFolder === null && isSpamOrTrashFolder(folders, folderInfo.folder)))
@ -51,12 +51,12 @@ export async function showEditFolderDialog(mailBoxDetail: MailboxDetail, editedF
items: targetFolders,
selectedValue: selectedParentFolder,
selectedValueDisplay: selectedParentFolder ? getFolderName(selectedParentFolder) : noParentFolderOption,
selectionChangedHandler: (newFolder: MailFolder | null) => (selectedParentFolder = newFolder),
selectionChangedHandler: (newFolder: MailSet | null) => (selectedParentFolder = newFolder),
helpLabel: () => (selectedParentFolder ? getPathToFolderString(folders, selectedParentFolder) : ""),
}),
]
async function getMailIdsGroupedByListId(folder: MailFolder): Promise<Map<Id, Id[]>> {
async function getMailIdsGroupedByListId(folder: MailSet): Promise<Map<Id, Id[]>> {
const mailSetEntries = await locator.entityClient.loadAll(MailSetEntryTypeRef, folder.entries)
return groupByAndMap(
mailSetEntries,
@ -65,7 +65,7 @@ export async function showEditFolderDialog(mailBoxDetail: MailboxDetail, editedF
)
}
async function loadAllMailsOfFolder(folder: MailFolder, reportableMails: Array<Mail>) {
async function loadAllMailsOfFolder(folder: MailSet, reportableMails: Array<Mail>) {
const mailIdsPerBag = await getMailIdsGroupedByListId(folder)
for (const [mailListId, mailIds] of mailIdsPerBag) {
reportableMails.push(...(await locator.entityClient.loadMultiple(MailTypeRef, mailListId, mailIds)))
@ -132,19 +132,13 @@ export async function showEditFolderDialog(mailBoxDetail: MailboxDetail, editedF
Dialog.showActionDialog({
title: editedFolder ? "editFolder_action" : "addFolder_action",
child: form,
validator: () => checkFolderName(mailBoxDetail, folders, folderNameValue, mailGroupId, selectedParentFolder?._id ?? null),
validator: () => checkFolderName(folders, folderNameValue, selectedParentFolder?._id ?? null),
allowOkWithReturn: true,
okAction: okAction,
})
}
function checkFolderName(
mailboxDetail: MailboxDetail,
folders: FolderSystem,
name: string,
mailGroupId: Id,
parentFolderId: IdTuple | null,
): TranslationKey | null {
function checkFolderName(folders: FolderSystem, name: string, parentFolderId: IdTuple | null): TranslationKey | null {
if (name.trim() === "") {
return "folderNameNeutral_msg"
} else if (folders.getCustomFoldersOfParent(parentFolderId).some((f) => f.name === name)) {

View file

@ -1,7 +1,7 @@
import { Dialog } from "../../../common/gui/base/Dialog"
import { TextField, TextFieldAttrs } from "../../../common/gui/base/TextField"
import m from "mithril"
import type { MailBox, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs"
import type { MailBox, MailSet } from "../../../common/api/entities/tutanota/TypeRefs"
import { isOfflineError } from "../../../common/api/common/utils/ErrorUtils"
import { LockedError, PreconditionFailedError } from "../../../common/api/common/error/RestError"
import { MailViewModel } from "./MailViewModel"
@ -11,7 +11,7 @@ import { showNotAvailableForFreeDialog } from "../../../common/misc/Subscription
const LIMIT_EXCEEDED_ERROR = "limitReached"
export async function showEditLabelDialog(mailbox: MailBox | null, mailViewModel: MailViewModel, label: MailFolder | null) {
export async function showEditLabelDialog(mailbox: MailBox | null, mailViewModel: MailViewModel, label: MailSet | null) {
let name = label ? label.name : ""
let color = label && label.color ? label.color : ""

View file

@ -3,7 +3,7 @@ import { modal, ModalComponent } from "../../../common/gui/base/Modal.js"
import { focusNext, focusPrevious, Shortcut } from "../../../common/misc/KeyManager.js"
import { BaseButton, BaseButtonAttrs } from "../../../common/gui/base/buttons/BaseButton.js"
import { PosRect, showDropdown } from "../../../common/gui/base/Dropdown.js"
import { MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { MailSet } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { component_size, size } from "../../../common/gui/size.js"
import { AllIcons, Icon, IconSize } from "../../../common/gui/base/Icon.js"
import { Icons } from "../../../common/gui/base/icons/Icons.js"
@ -27,9 +27,9 @@ export class LabelsPopup implements ModalComponent {
private readonly sourceElement: HTMLElement,
private readonly origin: PosRect,
private readonly width: number,
private readonly labelsForMails: ReadonlyMap<Id, ReadonlyArray<MailFolder>>,
private readonly labels: { label: MailFolder; state: LabelState }[],
private readonly onLabelsApplied: (addedLabels: MailFolder[], removedLabels: MailFolder[]) => unknown,
private readonly labelsForMails: ReadonlyMap<Id, ReadonlyArray<MailSet>>,
private readonly labels: { label: MailSet; state: LabelState }[],
private readonly onLabelsApplied: (addedLabels: MailSet[], removedLabels: MailSet[]) => unknown,
) {
this.view = this.view.bind(this)
this.oncreate = this.oncreate.bind(this)
@ -97,7 +97,16 @@ export class LabelsPopup implements ModalComponent {
opacity,
},
}),
m(".button-height.flex.items-center.ml-12.overflow-hidden", { style: { color, opacity } }, m(".text-ellipsis", label.name)),
m(
".button-height.flex.items-center.ml-12.overflow-hidden",
{
style: {
color,
opacity,
},
},
m(".text-ellipsis", label.name),
),
],
)
}),
@ -161,9 +170,9 @@ export class LabelsPopup implements ModalComponent {
return false
}
private getSortedLabels(): Record<"addedLabels" | "removedLabels", MailFolder[]> {
const removedLabels: MailFolder[] = []
const addedLabels: MailFolder[] = []
private getSortedLabels(): Record<"addedLabels" | "removedLabels", MailSet[]> {
const removedLabels: MailSet[] = []
const addedLabels: MailSet[] = []
for (const { label, state } of this.labels) {
if (state === LabelState.Applied) {
addedLabels.push(label)
@ -250,7 +259,7 @@ export class LabelsPopup implements ModalComponent {
modal.displayUnique(this, false)
}
private toggleLabel(labelState: { label: MailFolder; state: LabelState }) {
private toggleLabel(labelState: { label: MailSet; state: LabelState }) {
switch (labelState.state) {
case LabelState.AppliedToSome:
labelState.state = this.isMaxLabelsReached ? LabelState.NotApplied : LabelState.Applied

View file

@ -9,7 +9,7 @@ import { Icon, IconSize } from "../../../common/gui/base/Icon.js"
import { Icons } from "../../../common/gui/base/icons/Icons.js"
import { client } from "../../../common/misc/ClientDetector.js"
import { lang } from "../../../common/misc/LanguageViewModel.js"
import { MailFolder } from "../../../common/api/entities/tutanota/TypeRefs"
import { MailSet } from "../../../common/api/entities/tutanota/TypeRefs"
import { getFolderIcon } from "./MailGuiUtils"
export type MailFolderRowAttrs = {
@ -19,7 +19,7 @@ export type MailFolderRowAttrs = {
expanded: boolean | null
indentationLevel: number
onExpanderClick: (event: Event) => unknown
folder: MailFolder
folder: MailSet
hasChildren: boolean
onSelectedPath: boolean
numberOfPreviousRows: number

View file

@ -9,7 +9,7 @@ import { isSelectedPrefix, NavButtonAttrs, NavButtonColor } from "../../../commo
import { MAIL_PREFIX } from "../../../common/misc/RouteChange.js"
import { MailFolderRow } from "./MailFolderRow.js"
import { last, Thunk } from "@tutao/tutanota-utils"
import { MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { MailSet } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { attachDropdown, DropdownButtonAttrs } from "../../../common/gui/base/Dropdown.js"
import { Icons } from "../../../common/gui/base/icons/Icons.js"
import { ButtonColor } from "../../../common/gui/base/Button.js"
@ -29,12 +29,12 @@ export interface MailFolderViewAttrs {
mailModel: MailModel
mailboxDetail: MailboxDetail
mailFolderElementIdToSelectedMailId: ReadonlyMap<Id, Id>
onFolderClick: (folder: MailFolder) => unknown
onFolderDrop: (dropData: DropData, folder: MailFolder) => unknown
onFolderClick: (folder: MailSet) => unknown
onFolderDrop: (dropData: DropData, folder: MailSet) => unknown
expandedFolders: ReadonlySet<Id>
onFolderExpanded: (folder: MailFolder, state: boolean) => unknown
onShowFolderAddEditDialog: (mailGroupId: Id, folder: MailFolder | null, parentFolder: MailFolder | null) => unknown
onDeleteCustomMailFolder: (folder: MailFolder) => unknown
onFolderExpanded: (folder: MailSet, state: boolean) => unknown
onShowFolderAddEditDialog: (mailGroupId: Id, folder: MailSet | null, parentFolder: MailSet | null) => unknown
onDeleteCustomMailFolder: (folder: MailSet) => unknown
inEditMode: boolean
onEditMailbox: () => unknown
}
@ -88,7 +88,7 @@ export class MailFoldersView implements Component<MailFolderViewAttrs> {
groupCounters: Counters,
folders: FolderSystem,
attrs: MailFolderViewAttrs,
path: MailFolder[],
path: MailSet[],
isInternalUser: boolean,
indentationLevel: number = 0,
): { children: Children[]; numRows: number } {
@ -207,7 +207,7 @@ export class MailFoldersView implements Component<MailFolderViewAttrs> {
* Get a full path to a folder with colons in between,
* Used for data-testids
*/
private getPathToFolderAsString(folderSystem: FolderSystem, currentFolder: MailFolder): string {
private getPathToFolderAsString(folderSystem: FolderSystem, currentFolder: MailSet): string {
return folderSystem
.getPathToFolder(currentFolder._id)
.map((f) => getFolderName(f))
@ -219,7 +219,7 @@ export class MailFoldersView implements Component<MailFolderViewAttrs> {
return (counters[counterId] ?? 0) + system.children.reduce((acc, child) => acc + this.getTotalFolderCounter(counters, child), 0)
}
private createFolderMoreButton(folder: MailFolder, folders: FolderSystem, attrs: MailFolderViewAttrs, onClose: Thunk): IconButtonAttrs {
private createFolderMoreButton(folder: MailSet, folders: FolderSystem, attrs: MailFolderViewAttrs, onClose: Thunk): IconButtonAttrs {
return attachDropdown({
mainButtonAttrs: {
title: "more_label",
@ -250,7 +250,7 @@ export class MailFoldersView implements Component<MailFolderViewAttrs> {
})
}
private deleteButtonAttrs(attrs: MailFolderViewAttrs, folder: MailFolder): DropdownButtonAttrs {
private deleteButtonAttrs(attrs: MailFolderViewAttrs, folder: MailSet): DropdownButtonAttrs {
return {
label: "delete_action",
icon: Icons.Trash,
@ -260,7 +260,7 @@ export class MailFoldersView implements Component<MailFolderViewAttrs> {
}
}
private addButtonAttrs(attrs: MailFolderViewAttrs, folder: MailFolder): DropdownButtonAttrs {
private addButtonAttrs(attrs: MailFolderViewAttrs, folder: MailSet): DropdownButtonAttrs {
return {
label: "addFolder_action",
icon: Icons.Add,
@ -270,7 +270,7 @@ export class MailFoldersView implements Component<MailFolderViewAttrs> {
}
}
private editButtonAttrs(attrs: MailFolderViewAttrs, folders: FolderSystem, folder: MailFolder): DropdownButtonAttrs {
private editButtonAttrs(attrs: MailFolderViewAttrs, folders: FolderSystem, folder: MailSet): DropdownButtonAttrs {
return {
label: "edit_action",
icon: Icons.Edit,
@ -284,7 +284,7 @@ export class MailFoldersView implements Component<MailFolderViewAttrs> {
}
}
private renderCreateFolderAddButton(parentFolder: MailFolder | null, attrs: MailFolderViewAttrs): Child {
private renderCreateFolderAddButton(parentFolder: MailSet | null, attrs: MailFolderViewAttrs): Child {
return m(IconButton, {
title: "addFolder_action",
click: () => {

View file

@ -1,5 +1,5 @@
import type { MailboxModel } from "../../../common/mailFunctionality/MailboxModel.js"
import { File as TutanotaFile, Mail, MailFolder, MovedMails } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { File as TutanotaFile, Mail, MailSet, MovedMails } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { LockedError, PreconditionFailedError } from "../../../common/api/common/error/RestError"
import { Dialog } from "../../../common/gui/base/Dialog"
import { AllIcons } from "../../../common/gui/base/Icon"
@ -85,7 +85,7 @@ interface MoveMailsParams {
mailModel: MailModel
undoModel: UndoModel
mailIds: ReadonlyArray<IdTuple>
targetFolder: MailFolder
targetFolder: MailSet
moveMode: MoveMode
}
@ -274,7 +274,7 @@ export async function moveMailsToSystemFolder({
mailModel: MailModel
mailIds: ReadonlyArray<IdTuple>
targetFolderType: SystemFolderType
currentFolder: MailFolder
currentFolder: MailSet
moveMode: MoveMode
undoModel: UndoModel
}): Promise<boolean> {
@ -349,7 +349,7 @@ export function getFolderIconByType(folderType: MailSetKind): AllIcons {
}
}
export function getFolderIcon(folder: MailFolder): AllIcons {
export function getFolderIcon(folder: MailSet): AllIcons {
return getFolderIconByType(getMailFolderType(folder))
}
@ -503,7 +503,7 @@ export async function showMoveMailsFromFolderDropdown(
mailModel: MailModel,
undoModel: UndoModel,
origin: PosRect,
currentFolder: MailFolder,
currentFolder: MailSet,
mails: LazyMailIdResolver,
moveMode: MoveMode,
opts?: ShowMoveMailsDropdownOpts,

View file

@ -3,7 +3,7 @@ import { lang } from "../../../common/misc/LanguageViewModel"
import { Keys, MailSetKind, MailState, SystemFolderType } from "../../../common/api/common/TutanotaConstants"
import type { Mail } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { component_size, size } from "../../../common/gui/size"
import { component_size } from "../../../common/gui/size"
import { styles } from "../../../common/gui/styles"
import { Icon } from "../../../common/gui/base/Icon"
import { Icons } from "../../../common/gui/base/icons/Icons"
@ -406,10 +406,8 @@ export class MailListView implements Component<MailListViewAttrs> {
const selectedFolder = this.mailViewModel.getFolder()
if (selectedFolder) {
const mailDetails = await this.mailViewModel.getMailboxDetails()
if (mailDetails.mailbox.folders) {
const folders = await mailLocator.mailModel.getMailboxFoldersForId(mailDetails.mailbox.folders._id)
return isOfTypeOrSubfolderOf(folders, selectedFolder, MailSetKind.ARCHIVE) || selectedFolder.folderType === MailSetKind.TRASH
}
const folders = await mailLocator.mailModel.getMailboxFoldersForId(mailDetails.mailbox.folders._id)
return isOfTypeOrSubfolderOf(folders, selectedFolder, MailSetKind.ARCHIVE) || selectedFolder.folderType === MailSetKind.TRASH
}
return false
}

View file

@ -1,6 +1,6 @@
import { getMailFolderType, MailSetKind, MailState, ReplyType } from "../../../common/api/common/TutanotaConstants"
import { FontIcons } from "../../../common/gui/base/icons/FontIcons"
import type { Mail, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import type { Mail, MailSet } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { formatTimeOrDateOrYesterday } from "../../../common/misc/Formatter.js"
import m, { Children } from "mithril"
import Badge from "../../../common/gui/base/Badge"
@ -74,7 +74,7 @@ export class MailRow implements VirtualRow<Mail> {
constructor(
private readonly showFolderIcon: boolean,
private readonly getLabelsForMail: (mail: Mail) => ReadonlyArray<MailFolder>,
private readonly getLabelsForMail: (mail: Mail) => ReadonlyArray<MailSet>,
private readonly onSelected: (mail: Mail, selected: boolean) => unknown,
private readonly getHighlightedStrings?: () => readonly SearchToken[],
) {
@ -141,7 +141,7 @@ export class MailRow implements VirtualRow<Mail> {
}
}
private updateLabels(mail: Mail): readonly MailFolder[] {
private updateLabels(mail: Mail): readonly MailSet[] {
const labels = this.getLabelsForMail(mail)
for (const [i, element] of this.labelsDom.entries()) {

View file

@ -5,7 +5,7 @@ import { lang } from "../../../common/misc/LanguageViewModel"
import { Dialog } from "../../../common/gui/base/Dialog"
import { FeatureType, getMailFolderType, Keys, MailReportType, MailSetKind, SystemFolderType } from "../../../common/api/common/TutanotaConstants"
import { AppHeaderAttrs, Header } from "../../../common/gui/Header.js"
import { Mail, MailBox, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { Mail, MailBox, MailSet } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { assertNotNull, first, getFirstOrThrow, isEmpty, isNotEmpty, noOp, ofClass } from "@tutao/tutanota-utils"
import { MailListView } from "./MailListView"
import { assertMainOrNode, isApp } from "../../../common/api/common/Env"
@ -1015,7 +1015,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
})
}
private setExpandedState(folder: MailFolder, currentExpansionState: boolean) {
private setExpandedState(folder: MailSet, currentExpansionState: boolean) {
if (currentExpansionState) {
this.expandedState.delete(getElementId(folder))
} else {
@ -1112,7 +1112,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
return []
}
private async handleLabelMailDrop(dropData: MailDropData, targetLabel: MailFolder): Promise<void> {
private async handleLabelMailDrop(dropData: MailDropData, targetLabel: MailSet): Promise<void> {
const mailsToAddLabel = this.getDroppedMails(dropData)
if (!isEmpty(mailsToAddLabel)) {
@ -1121,7 +1121,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
}
}
private async handleFolderInFolderDrop(dropData: FolderDropData, targetFolder: MailFolder) {
private async handleFolderInFolderDrop(dropData: FolderDropData, targetFolder: MailSet) {
if (
// dragging to Trash/Spam can be added at a later point
targetFolder.folderType === MailSetKind.TRASH ||
@ -1148,7 +1148,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
await mailLocator.mailModel.setParentForFolder(folderToMove, targetFolder._id)
}
private async handleFolderMailDrop(dropData: MailDropData, targetFolder: MailFolder) {
private async handleFolderMailDrop(dropData: MailDropData, targetFolder: MailSet) {
const currentFolder = this.mailViewModel.getFolder()
if (!currentFolder) {
@ -1171,7 +1171,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
}
}
private async handeFolderFileDrop(dropData: FileDropData, mailboxDetail: MailboxDetail, mailFolder: MailFolder) {
private async handeFolderFileDrop(dropData: FileDropData, mailboxDetail: MailboxDetail, mailFolder: MailSet) {
function droppedOnlyMailFiles(files: Array<File>): boolean {
// there's similar logic on the AttachmentBubble, but for natively shared files.
return files.every((f) => f.name.endsWith(".eml") || f.name.endsWith(".mbox"))
@ -1217,16 +1217,13 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
dialog?.show()
}
private async deleteCustomMailFolder(mailboxDetail: MailboxDetail, folder: MailFolder): Promise<void> {
private async deleteCustomMailFolder(mailboxDetail: MailboxDetail, folder: MailSet): Promise<void> {
if (folder.folderType !== MailSetKind.CUSTOM) {
throw new Error("Cannot delete non-custom folder: " + String(folder._id))
}
// remove any selection to avoid that the next mail is loaded and selected for each deleted mail event
this.mailViewModel?.listModel?.selectNone()
if (mailboxDetail.mailbox.folders == null) {
return
}
const folders = await mailLocator.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
if (isSpamOrTrashFolder(folders, folder)) {
@ -1286,7 +1283,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
}
}
private async showFolderAddEditDialog(mailGroupId: Id, folder: MailFolder | null, parentFolder: MailFolder | null) {
private async showFolderAddEditDialog(mailGroupId: Id, folder: MailSet | null, parentFolder: MailSet | null) {
const mailboxDetail = await locator.mailboxModel.getMailboxDetailsForMailGroup(mailGroupId)
await showEditFolderDialog(mailboxDetail, folder, parentFolder)
}
@ -1295,11 +1292,11 @@ export class MailView extends BaseTopLevelView implements TopLevelView<MailViewA
await showEditLabelDialog(mailbox, this.mailViewModel, null)
}
private async showLabelEditDialog(label: MailFolder) {
private async showLabelEditDialog(label: MailSet) {
await showEditLabelDialog(null, this.mailViewModel, label)
}
private async showLabelDeleteDialog(label: MailFolder) {
private async showLabelDeleteDialog(label: MailSet) {
const confirmed = await Dialog.confirm(
lang.getTranslation("confirmDeleteLabel_msg", {
"{1}": label.name,

View file

@ -6,8 +6,8 @@ import {
ImportMailStateTypeRef,
Mail,
MailBox,
MailFolder,
MailFolderTypeRef,
MailSet,
MailSetTypeRef,
MailSetEntry,
MailSetEntryTypeRef,
MailTypeRef,
@ -61,7 +61,7 @@ export interface UndoAction {
/** ViewModel for the overall mail view. */
export class MailViewModel {
/** Beware: this can be a label. */
private _folder: MailFolder | null = null
private _folder: MailSet | null = null
private _listModel: MailSetListModel | null = null
/** id of the mail requested to be displayed, independent of the list state. */
private stickyMailId: IdTuple | null = null
@ -85,6 +85,7 @@ export class MailViewModel {
private currentShowTargetMarker: object = {}
/* We only attempt counter fixup once after switching folders and loading the list fully. */
private shouldAttemptCounterFixup: boolean = true
constructor(
private readonly mailboxModel: MailboxModel,
private readonly mailModel: MailModel,
@ -168,7 +169,7 @@ export class MailViewModel {
}
}
private async showMail(folder?: MailFolder | null, mailId?: Id) {
private async showMail(folder?: MailSet | null, mailId?: Id) {
// an optimization to not open an email that we already display
if (
folder != null &&
@ -229,7 +230,7 @@ export class MailViewModel {
}
}
private async selectFolderToUse(folderArgument: MailFolder | null): Promise<MailFolder> {
private async selectFolderToUse(folderArgument: MailSet | null): Promise<MailSet> {
if (folderArgument) {
const mailboxDetail = await this.mailModel.getMailboxDetailsForMailFolder(folderArgument)
if (mailboxDetail) {
@ -306,7 +307,7 @@ export class MailViewModel {
return changed
}
private async loadAndSelectMail(folder: MailFolder, mailId: Id) {
private async loadAndSelectMail(folder: MailSet, mailId: Id) {
let pagesLoaded = 0
const foundMail = await this.listModel?.loadAndSelect(
mailId,
@ -368,6 +369,7 @@ export class MailViewModel {
return await this.getResolvedMails(actionableMails)
}
clearStickyMail() {
if (this.stickyMailId) {
this.stickyMailId = null
@ -388,13 +390,14 @@ export class MailViewModel {
return currentFolder != null && (currentFolder.folderType === MailSetKind.TRASH || currentFolder.folderType === MailSetKind.SPAM)
}
}
isExportingMailsAllowed(): boolean {
return this.mailModel.isExportingMailsAllowed() && !client.isMobileDevice()
}
private async getFolderForUserInbox(): Promise<MailFolder> {
private async getFolderForUserInbox(): Promise<MailSet> {
const mailboxDetail = await this.mailboxModel.getUserMailboxDetails()
const folders = await this.mailModel.getMailboxFoldersForId(assertNotNull(mailboxDetail.mailbox.folders)._id)
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
return assertSystemFolderOfType(folders, MailSetKind.INBOX)
}
@ -439,19 +442,19 @@ export class MailViewModel {
/**
* Beware: this can return a label.
*/
getFolder(): MailFolder | null {
getFolder(): MailSet | null {
return this._folder
}
getLabelsForMail(mail: Mail): ReadonlyArray<MailFolder> {
getLabelsForMail(mail: Mail): ReadonlyArray<MailSet> {
return this.listModel?.getLabelsForMail(mail) ?? []
}
async applyLabelToMails(mails: readonly IdTuple[], label: MailFolder): Promise<void> {
async applyLabelToMails(mails: readonly IdTuple[], label: MailSet): Promise<void> {
await this.mailModel.applyLabels(mails, [label], [])
}
private setListId(folder: MailFolder) {
private setListId(folder: MailSet) {
const oldFolderId = this._folder?._id
// update the folder just in case, maybe it got updated
this._folder = folder
@ -513,9 +516,9 @@ export class MailViewModel {
this.shouldAttemptCounterFixup = true
}
private fixCounterIfNeeded: (folder: MailFolder, loadedMailsWhenCalled: ReadonlyArray<Mail>) => void = debounce(
private fixCounterIfNeeded: (folder: MailSet, loadedMailsWhenCalled: ReadonlyArray<Mail>) => void = debounce(
2000,
async (folder: MailFolder, loadedMailsWhenCalled: ReadonlyArray<Mail>) => {
async (folder: MailSet, loadedMailsWhenCalled: ReadonlyArray<Mail>) => {
// If folders are changed, the list won't have the data we need.
// Do not rely on counters if we are not connected.
// We can't know the correct unreadMailCount if some unread mails are filtered out.
@ -662,7 +665,7 @@ export class MailViewModel {
private async processImportedMails(update: EntityUpdateData<ImportMailState>) {
const importMailState = await this.entityClient.load(ImportMailStateTypeRef, [update.instanceListId, update.instanceId])
const importedFolder = await this.entityClient.load(MailFolderTypeRef, importMailState.targetFolder)
const importedFolder = await this.entityClient.load(MailSetTypeRef, importMailState.targetFolder)
let status = parseInt(importMailState.status) as ImportStatus
@ -715,7 +718,7 @@ export class MailViewModel {
if (this.currentShowTargetMarker !== state) {
return
}
if (mailboxDetail == null || mailboxDetail.mailbox.folders == null) {
if (mailboxDetail == null) {
return
}
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
@ -735,7 +738,7 @@ export class MailViewModel {
if (!this._folder) return false
const mailboxDetail = await this.mailModel.getMailboxDetailsForMailFolder(this._folder)
const selectedFolder = this.getFolder()
if (selectedFolder && mailboxDetail && mailboxDetail.mailbox.folders) {
if (selectedFolder && mailboxDetail) {
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
return isOfTypeOrSubfolderOf(folders, selectedFolder, MailSetKind.DRAFT)
} else {
@ -747,7 +750,7 @@ export class MailViewModel {
const folder = this.getFolder()
if (folder) {
const mailboxDetail = await this.mailModel.getMailboxDetailsForMailFolder(folder)
if (folder && mailboxDetail && mailboxDetail.mailbox.folders) {
if (folder && mailboxDetail) {
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
return isSpamOrTrashFolder(folders, folder)
}
@ -755,12 +758,12 @@ export class MailViewModel {
return false
}
private async mailboxDetailForListWithFallback(folder?: MailFolder | null) {
private async mailboxDetailForListWithFallback(folder?: MailSet | null) {
const mailboxDetailForListId = folder ? await this.mailModel.getMailboxDetailsForMailFolder(folder) : null
return mailboxDetailForListId ?? (await this.mailboxModel.getUserMailboxDetails())
}
async finallyDeleteAllMailsInSelectedFolder(folder: MailFolder): Promise<void> {
async finallyDeleteAllMailsInSelectedFolder(folder: MailSet): Promise<void> {
// remove any selection to avoid that the next mail is loaded and selected for each deleted mail event
this.listModel?.selectNone()
@ -774,7 +777,7 @@ export class MailViewModel {
}),
)
} else {
const folders = await this.mailModel.getMailboxFoldersForId(assertNotNull(mailboxDetail.mailbox.folders)._id)
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
if (isSubfolderOfType(folders, folder, MailSetKind.TRASH) || isSubfolderOfType(folders, folder, MailSetKind.SPAM)) {
return this.mailModel.finallyDeleteCustomMailFolder(folder).catch(
ofClass(PreconditionFailedError, () => {
@ -843,11 +846,11 @@ export class MailViewModel {
await this.mailModel.createLabel(assertNotNull(mailbox._ownerGroup), labelData)
}
async editLabel(label: MailFolder, newData: { name: string; color: string }) {
async editLabel(label: MailSet, newData: { name: string; color: string }) {
await this.mailModel.updateLabel(label, newData)
}
async deleteLabel(label: MailFolder) {
async deleteLabel(label: MailSet) {
await this.mailModel.deleteLabel(label)
}
@ -855,11 +858,11 @@ export class MailViewModel {
* Returns true if mails should be grouped by conversation in the mail list based on user preference and a folder
* @param folder the folder to check or, by default, the current folder
*/
groupMailsByConversation(folder: MailFolder | null = this._folder) {
groupMailsByConversation(folder: MailSet | null = this._folder) {
return this.mailModel.canUseConversationView() && listByConversationInFolder(this.conversationPrefProvider, folder)
}
getMoveMode(folder: MailFolder): MoveMode {
getMoveMode(folder: MailSet): MoveMode {
return this.groupMailsByConversation(folder) ? MoveMode.Conversation : MoveMode.Mails
}
}
@ -867,7 +870,7 @@ export class MailViewModel {
/**
* @return true if mails should be grouped by conversation in the mail list based on user preference and a given {@param folder}
*/
export function listByConversationInFolder(conversationPrefProvider: ConversationPrefProvider, folder: MailFolder | null): boolean {
export function listByConversationInFolder(conversationPrefProvider: ConversationPrefProvider, folder: MailSet | null): boolean {
const onlySelectedMailInViewer = conversationPrefProvider.getConversationViewShowOnlySelectedMail()
const prefersConversationInList = !onlySelectedMailInViewer && conversationPrefProvider.getMailListDisplayMode() === MailListDisplayMode.CONVERSATIONS

View file

@ -5,7 +5,7 @@ import {
Mail,
MailAddress,
MailDetails,
MailFolder,
MailSet,
MailTypeRef,
} from "../../../common/api/entities/tutanota/TypeRefs.js"
import {
@ -231,7 +231,7 @@ export class MailViewerViewModel {
if (folder) {
this.mailModel.getMailboxDetailsForMail(this.mail).then(async (mailboxDetails) => {
if (mailboxDetails == null || mailboxDetails.mailbox.folders == null) {
if (mailboxDetails == null) {
return
}
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetails.mailbox.folders._id)
@ -563,7 +563,7 @@ export class MailViewerViewModel {
try {
const mailboxDetail = await this.mailModel.getMailboxDetailsForMail(this.mail)
// We should always have a mailbox, the check above throws due AssertNotNull in response.
if (mailboxDetail == null || mailboxDetail.mailbox.folders == null) {
if (mailboxDetail == null) {
return
}
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
@ -682,6 +682,7 @@ export class MailViewerViewModel {
return encodedText
})
}
async determineUnsubscribeOrder(): Promise<Array<UnsubscribeAction>> {
const mailHeaders = await this.getHeaders()
const unsubscribeActions: Array<UnsubscribeAction> = []
@ -1268,7 +1269,7 @@ export class MailViewerViewModel {
this.collapsed = true
}
getLabels(): readonly MailFolder[] {
getLabels(): readonly MailSet[] {
return this.mailModel.getLabelsForMail(this.mail).sort((labelA, labelB) => labelA.name.localeCompare(labelB.name))
}

View file

@ -21,7 +21,7 @@ export class OpenMailboxHandler {
if (this.logins.isUserLoggedIn() && this.logins.getUserController().user._id === userId) {
if (!requestedPath) {
const [mailboxDetail] = await this.mailboxModel.getMailboxDetails()
const folders = await this.mailModel.getMailboxFoldersForId(assertNotNull(mailboxDetail.mailbox.folders)._id)
const folders = await this.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
const inbox = assertSystemFolderOfType(folders, MailSetKind.INBOX)
m.route.set("/mail/" + getElementId(inbox))
} else {

View file

@ -7,7 +7,7 @@ import { List, ListAttrs, MultiselectMode, RenderConfig } from "../../../common/
import { component_size, size } from "../../../common/gui/size.js"
import { KindaContactRow } from "../../contacts/view/ContactListView.js"
import { SearchableTypes } from "./SearchViewModel.js"
import { CalendarEvent, CalendarEventTypeRef, Contact, ContactTypeRef, Mail, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { CalendarEvent, CalendarEventTypeRef, Contact, ContactTypeRef, Mail, MailSet } from "../../../common/api/entities/tutanota/TypeRefs.js"
import ColumnEmptyMessageBox from "../../../common/gui/base/ColumnEmptyMessageBox.js"
import { BootIcons } from "../../../common/gui/base/icons/BootIcons.js"
import { theme } from "../../../common/gui/theme.js"
@ -36,7 +36,7 @@ export interface SearchListViewAttrs {
currentType: TypeRef<Mail | Contact | CalendarEvent>
isFreeAccount: boolean
cancelCallback: () => unknown | null
getLabelsForMail: (mail: Mail) => MailFolder[]
getLabelsForMail: (mail: Mail) => MailSet[]
highlightedStrings: readonly SearchToken[]
availableCalendars: ReadonlyArray<CalendarInfoBase>
}

View file

@ -2,7 +2,7 @@ import { ListElementListModel } from "../../../common/misc/ListElementListModel.
import { SearchResultListEntry } from "./SearchListView.js"
import { SearchRestriction, SearchResult } from "../../../common/api/worker/search/SearchTypes.js"
import { EntityEventsListener, EventController } from "../../../common/api/main/EventController.js"
import { CalendarEvent, CalendarEventTypeRef, Contact, ContactTypeRef, Mail, MailFolder, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { CalendarEvent, CalendarEventTypeRef, Contact, ContactTypeRef, Mail, MailSet, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { ListElementEntity } from "../../../common/api/common/EntityTypes.js"
import { FULL_INDEXED_TIMESTAMP, MailSetKind, NOTHING_INDEXED_TIMESTAMP, OperationType } from "../../../common/api/common/TutanotaConstants.js"
import {
@ -1078,7 +1078,7 @@ export class SearchViewModel {
this.eventController.removeEntityListener(this.entityEventsListener)
}
getLabelsForMail(mail: Mail): MailFolder[] {
getLabelsForMail(mail: Mail): MailSet[] {
return mailLocator.mailModel.getLabelsForMail(mail)
}
}

View file

@ -37,7 +37,7 @@ export type InboxRuleTemplate = Pick<InboxRule, "type" | "value"> & {
export async function show(mailBoxDetail: MailboxDetail, ruleOrTemplate: InboxRuleTemplate) {
if (locator.logins.getUserController().isFreeAccount()) {
showNotAvailableForFreeDialog()
} else if (mailBoxDetail && mailBoxDetail.mailbox.folders) {
} else if (mailBoxDetail) {
const folders = await mailLocator.mailModel.getMailboxFoldersForId(mailBoxDetail.mailbox.folders._id)
let targetFolders = folders.getIndentedList().map((folderInfo: IndentedFolder) => {
return {

View file

@ -9,7 +9,7 @@ import { AvailablePlanType, HighestTierPlans, ImportStatus, MailSetKind } from "
import { IndentedFolder } from "../../common/api/common/mail/FolderSystem"
import { lang, TranslationKey } from "../../common/misc/LanguageViewModel"
import { MailImporter, UiImportStatus } from "../mail/import/MailImporter.js"
import { MailFolder } from "../../common/api/entities/tutanota/TypeRefs"
import { MailSet } from "../../common/api/entities/tutanota/TypeRefs"
import { elementIdPart, generatedIdToTimestamp, isSameId, sortCompareByReverseId } from "../../common/api/common/utils/EntityUtils"
import { Icons } from "../../common/gui/base/icons/Icons.js"
import { DropDownSelector, SelectorItemList } from "../../common/gui/base/DropDownSelector.js"
@ -85,7 +85,7 @@ export class DesktopMailImportSettingsViewer implements UpdatableSettingsViewer
// but at least we won't block inbox ( incoming new mails )
const selectableFolders = folders.getIndentedList().filter((folderInfo) => folderInfo.folder.folderType !== MailSetKind.INBOX)
let targetFolders: SelectorItemList<MailFolder | null> = selectableFolders.map((folderInfo: IndentedFolder) => {
let targetFolders: SelectorItemList<MailSet | null> = selectableFolders.map((folderInfo: IndentedFolder) => {
return {
name: getIndentedFolderNameForDropdown(folderInfo),
value: folderInfo.folder,
@ -97,7 +97,7 @@ export class DesktopMailImportSettingsViewer implements UpdatableSettingsViewer
disabled: this.mailImporter().shouldRenderImportStatus(),
selectedValue: selectedTargetFolder,
selectedValueDisplay: selectedTargetFolder ? getFolderName(selectedTargetFolder) : loadingMsg,
selectionChangedHandler: (newFolder: MailFolder | null) => (this.mailImporter().selectedTargetFolder = newFolder),
selectionChangedHandler: (newFolder: MailSet | null) => (this.mailImporter().selectedTargetFolder = newFolder),
helpLabel: () => helpLabel,
})
} else {

View file

@ -4,7 +4,7 @@ import { lang, type MaybeTranslation } from "../../common/misc/LanguageViewModel
import type { MailboxGroupRoot, MailboxProperties, OutOfOfficeNotification, TutanotaProperties } from "../../common/api/entities/tutanota/TypeRefs.js"
import {
MailboxPropertiesTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
OutOfOfficeNotificationTypeRef,
TutanotaPropertiesTypeRef,
} from "../../common/api/entities/tutanota/TypeRefs.js"
@ -517,7 +517,7 @@ export class MailSettingsViewer implements UpdatableSettingsViewer {
}
private async getTextForTarget(mailboxDetail: MailboxDetail, targetFolderId: IdTuple): Promise<string> {
const folders = await mailLocator.mailModel.getMailboxFoldersForId(assertNotNull(mailboxDetail.mailbox.folders)._id)
const folders = await mailLocator.mailModel.getMailboxFoldersForId(mailboxDetail.mailbox.folders._id)
let folder = folders.getFolderById(elementIdPart(targetFolderId))
if (folder) {
@ -534,7 +534,7 @@ export class MailSettingsViewer implements UpdatableSettingsViewer {
const props = await mailLocator.entityClient.load(TutanotaPropertiesTypeRef, mailLocator.logins.getUserController().props._id)
this._updateTutanotaPropertiesSettings(props)
this._updateInboxRules(props)
} else if (isUpdateForTypeRef(MailFolderTypeRef, update)) {
} else if (isUpdateForTypeRef(MailSetTypeRef, update)) {
this._updateInboxRules(mailLocator.logins.getUserController().props)
} else if (isUpdateForTypeRef(OutOfOfficeNotificationTypeRef, update)) {
this._outOfOfficeNotification.reload().then(() => this._updateOutOfOfficeNotification())

View file

@ -17,7 +17,7 @@ import {
MailDetails,
MailDetailsBlobTypeRef,
MailDetailsDraftTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
MailSetEntry,
MailSetEntryTypeRef,
MailTypeRef,
@ -517,7 +517,7 @@ export class MailIndexer {
* Provides all mail set list ids of the given mailbox
*/
private async loadMailFolderListIds(mailbox: MailBox): Promise<Id[]> {
const mailSets = await this.entityClient.loadAll(MailFolderTypeRef, assertNotNull(mailbox.folders).folders)
const mailSets = await this.entityClient.loadAll(MailSetTypeRef, mailbox.folders.folders)
return mailSets.filter(isFolder).map((set) => set.entries)
}

View file

@ -13,7 +13,7 @@ import {
MailBoxTypeRef,
MailDetailsBlobTypeRef,
MailDetailsDraftTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
MailSetEntryTypeRef,
MailTypeRef,
} from "../../../common/api/entities/tutanota/TypeRefs.js"
@ -40,7 +40,7 @@ export class MailOfflineCleaner implements OfflineStorageCleaner {
const mailBoxes = await offlineStorage.getElementsOfType(MailBoxTypeRef)
for (const mailBox of mailBoxes) {
const currentMailBag = assertNotNull(mailBox.currentMailBag)
const folders = await offlineStorage.getWholeList(MailFolderTypeRef, mailBox.folders!.folders)
const folders = await offlineStorage.getWholeList(MailSetTypeRef, mailBox.folders.folders)
// Deleting MailSetEntries first to make sure that once we start deleting Mail
// we don't have any MailSetEntries that reference that Mail anymore.
for (const mailSet of folders) {

View file

@ -9,8 +9,8 @@ import {
MailBox,
MailboxGroupRootTypeRef,
MailBoxTypeRef,
MailFolder,
MailFolderTypeRef,
MailSet,
MailSetTypeRef,
MailTypeRef,
PopulateClientSpamTrainingDatum,
} from "../../../common/api/entities/tutanota/TypeRefs"
@ -47,11 +47,7 @@ export class SpamClassifierDataDealer {
public async fetchAllTrainingData(ownerGroup: Id): Promise<TrainingDataset> {
const mailboxGroupRoot = await this.entityClient.load(MailboxGroupRootTypeRef, ownerGroup)
const mailbox = await this.entityClient.load(MailBoxTypeRef, mailboxGroupRoot.mailbox)
const mailSets = await this.entityClient.loadAll(MailFolderTypeRef, assertNotNull(mailbox.folders).folders)
if (mailbox.clientSpamTrainingData == null || mailbox.modifiedClientSpamTrainingDataIndex == null) {
return { trainingData: [], lastTrainingDataIndexId: GENERATED_MIN_ID, hamCount: 0, spamCount: 0 }
}
const mailSets = await this.entityClient.loadAll(MailSetTypeRef, mailbox.folders.folders)
// clientSpamTrainingData is NOT cached
let clientSpamTrainingData = await this.entityClient.loadAll(ClientSpamTrainingDatumTypeRef, mailbox.clientSpamTrainingData)
@ -102,9 +98,6 @@ export class SpamClassifierDataDealer {
const mailbox = await this.entityClient.load(MailBoxTypeRef, mailboxGroupRoot.mailbox)
const emptyResult = { trainingData: [], lastTrainingDataIndexId: indexStartId, hamCount: 0, spamCount: 0 }
if (mailbox.clientSpamTrainingData == null || mailbox.modifiedClientSpamTrainingDataIndex == null) {
return emptyResult
}
const modifiedClientSpamTrainingDataIndicesSinceStart = await this.entityClient.loadRange(
ClientSpamTrainingDatumIndexEntryTypeRef,
@ -181,7 +174,7 @@ export class SpamClassifierDataDealer {
}
// Visible for testing
async fetchMailsByMailbagAfterDate(mailbag: MailBag, mailSets: MailFolder[], startDate: Date): Promise<Array<Mail>> {
async fetchMailsByMailbagAfterDate(mailbag: MailBag, mailSets: MailSet[], startDate: Date): Promise<Array<Mail>> {
const mails = await this.entityClient.loadAll(MailTypeRef, mailbag.mails, timestampToGeneratedId(startDate.getTime()))
const trashFolder = assertNotNull(mailSets.find((set) => getMailSetKind(set) === MailSetKind.TRASH))
return mails.filter((mail) => {
@ -190,7 +183,7 @@ export class SpamClassifierDataDealer {
})
}
private async fetchMailsForMailbox(mailbox: MailBox, mailSets: MailFolder[]): Promise<Array<Mail>> {
private async fetchMailsForMailbox(mailbox: MailBox, mailSets: MailSet[]): Promise<Array<Mail>> {
const downloadedMailClassificationData = new Array<Mail>()
const { LocalTimeDateProvider } = await import("../../../common/api/worker/DateProvider")
@ -210,12 +203,7 @@ export class SpamClassifierDataDealer {
return downloadedMailClassificationData
}
private async uploadTrainingDataForMails(mails: MailWithMailDetails[], mailBox: MailBox, mailSets: MailFolder[]): Promise<void> {
const clientSpamTrainingDataListId = mailBox.clientSpamTrainingData
if (clientSpamTrainingDataListId == null) {
return
}
private async uploadTrainingDataForMails(mails: MailWithMailDetails[], mailBox: MailBox, mailSets: MailSet[]): Promise<void> {
const unencryptedPopulateClientSpamTrainingData: UnencryptedPopulateClientSpamTrainingDatum[] = await promiseMap(
mails,
async (mailWithDetail) => {

View file

@ -1,26 +1,25 @@
import o from "@tutao/otest"
import { GroupType } from "../../src/common/api/common/TutanotaConstants.js"
import type { MailFolder } from "../../src/common/api/entities/tutanota/TypeRefs.js"
import type { MailSet } from "../../src/common/api/entities/tutanota/TypeRefs.js"
import {
ContactAddressTypeRef,
ContactListTypeRef,
ContactTypeRef,
MailBoxTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
MailSetEntryTypeRef,
} from "../../src/common/api/entities/tutanota/TypeRefs.js"
import { neverNull } from "@tutao/tutanota-utils"
import { initLocator, locator } from "../../src/mail-app/workerUtils/worker/WorkerLocator.js"
import { browserDataStub, createTestEntity } from "./TestUtils.js"
import { SessionType } from "../../src/common/api/common/SessionType.js"
function loadFolders(folderListId: Id): Promise<MailFolder[]> {
return locator.cachingEntityClient.loadAll(MailFolderTypeRef, folderListId)
function loadFolders(folderListId: Id): Promise<MailSet[]> {
return locator.cachingEntityClient.loadAll(MailSetTypeRef, folderListId)
}
function loadMailboxSystemFolders(): Promise<MailFolder[]> {
function loadMailboxSystemFolders(): Promise<MailSet[]> {
return locator.cachingEntityClient.loadRoot(MailBoxTypeRef, locator.user.getUserGroupId()).then((mailbox) => {
return loadFolders(neverNull(mailbox.folders).folders)
return loadFolders(mailbox.folders.folders)
})
}

View file

@ -42,8 +42,8 @@ import {
MailDetailsBlob,
MailDetailsBlobTypeRef,
MailDetailsTypeRef,
MailFolder,
MailFolderTypeRef,
MailSet,
MailSetTypeRef,
MailSetEntry,
MailSetEntryTypeRef,
MailTypeRef,
@ -933,16 +933,16 @@ o.spec("OfflineStorageDb", function () {
}),
)
await storage.put(MailBoxTypeRef, storableMailBox)
const storableSpamFolder = createTestEntity(MailFolderTypeRef, {
const storableSpamFolder = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", spamFolderId],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
entries: spamFolderEntriesId,
folderType: MailSetKind.SPAM,
})
await storage.put(MailFolderTypeRef, await toStorableInstance(storableSpamFolder))
await storage.put(MailSetTypeRef, await toStorableInstance(storableSpamFolder))
const storableTrashFolder = await toStorableInstance(
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", trashFolderId],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -950,7 +950,7 @@ o.spec("OfflineStorageDb", function () {
folderType: MailSetKind.TRASH,
}),
)
await storage.put(MailFolderTypeRef, storableTrashFolder)
await storage.put(MailSetTypeRef, storableTrashFolder)
})
o.test("ranges before timeRangeDays will be deleted", async function () {
@ -963,14 +963,14 @@ o.spec("OfflineStorageDb", function () {
const mailDetailsBlobId: IdTuple = ["mailDetailsList", "mailDetailsBlobId"]
const storableMailFolder = await toStorableInstance(
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "mailFolderId"],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
entries: listIdPart(mailSetEntryId),
}),
)
await storage.put(MailFolderTypeRef, storableMailFolder)
await storage.put(MailSetTypeRef, storableMailFolder)
const storableEntry = await toStorableInstance(
createTestEntity(MailSetEntryTypeRef, {
_id: mailSetEntryId,
@ -1035,7 +1035,7 @@ o.spec("OfflineStorageDb", function () {
const lowerMailSetEntryIdForRange = offsetMailSetEntryId(twoDaysBeforeTimeRangeDays, GENERATED_MIN_ID)
const upperMailSetEntryIdForRange = offsetMailSetEntryId(twoDaysAfterTimeRangeDays, GENERATED_MAX_ID)
const storableInbox = await toStorableInstance(
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "mailFolderId"],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -1043,7 +1043,7 @@ o.spec("OfflineStorageDb", function () {
folderType: MailSetKind.INBOX,
}),
)
await storage.put(MailFolderTypeRef, storableInbox)
await storage.put(MailSetTypeRef, storableInbox)
await storage.setNewRangeForList(MailSetEntryTypeRef, entriesListId, lowerMailSetEntryIdForRange, upperMailSetEntryIdForRange)
@ -1068,7 +1068,7 @@ o.spec("OfflineStorageDb", function () {
const lowerMailSetEntryIdForRange = offsetMailSetEntryId(oneDayAfterTimeRangeDays, GENERATED_MIN_ID)
const upperMailSetEntryIdForRange = offsetMailSetEntryId(twoDaysAfterTimeRangeDays, GENERATED_MAX_ID)
const storableCustomFolder = await toStorableInstance(
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "mailFolderId"],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -1076,7 +1076,7 @@ o.spec("OfflineStorageDb", function () {
folderType: MailSetKind.CUSTOM,
}),
)
await storage.put(MailFolderTypeRef, storableCustomFolder)
await storage.put(MailSetTypeRef, storableCustomFolder)
await storage.setNewRangeForList(MailSetEntryTypeRef, entriesListId, lowerMailSetEntryIdForRange, upperMailSetEntryIdForRange)
// Here we clear the excluded data
@ -1123,7 +1123,7 @@ o.spec("OfflineStorageDb", function () {
}),
)
const mailFolder = await toStorableInstance(
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "folderId"],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -1131,7 +1131,7 @@ o.spec("OfflineStorageDb", function () {
}),
)
await storage.put(MailFolderTypeRef, mailFolder)
await storage.put(MailSetTypeRef, mailFolder)
await storage.put(MailTypeRef, mail)
const storableSetEntry = await toStorableInstance(
createTestEntity(MailSetEntryTypeRef, {
@ -1168,7 +1168,7 @@ o.spec("OfflineStorageDb", function () {
upper: ensureBase64Ext(mailSetEntryTypeModel, upperMailSetEntryIdForRange),
})
const allFolderIds = await getAllIdsForType(MailFolderTypeRef)
const allFolderIds = await getAllIdsForType(MailSetTypeRef)
o.check(allFolderIds).deepEquals(["folderId", spamFolderId, trashFolderId])
const allMailIds = await getAllIdsForType(MailTypeRef)
o.check(allMailIds).deepEquals([elementIdPart(mailId)])
@ -1208,7 +1208,7 @@ o.spec("OfflineStorageDb", function () {
}),
)
const mailFolder = await toStorableInstance(
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "folderId"],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -1223,7 +1223,7 @@ o.spec("OfflineStorageDb", function () {
mail: mailId,
}),
)
await storage.put(MailFolderTypeRef, mailFolder)
await storage.put(MailSetTypeRef, mailFolder)
await storage.put(MailTypeRef, mail)
await storage.put(MailSetEntryTypeRef, storableMailSetEntry)
const storableDetails = await toStorableInstance(
@ -1252,7 +1252,7 @@ o.spec("OfflineStorageDb", function () {
upper: ensureBase64Ext(mailSetEntryTypeModel, upperMailSetEntryIdForRange),
})
const allFolderIds = await getAllIdsForType(MailFolderTypeRef)
const allFolderIds = await getAllIdsForType(MailSetTypeRef)
o.check(allFolderIds).deepEquals(["folderId", spamFolderId, trashFolderId])
const allMailIds = await getAllIdsForType(MailTypeRef)
o.check(allMailIds).deepEquals([])
@ -1336,7 +1336,7 @@ o.spec("OfflineStorageDb", function () {
const trashSubfolderMailSetEntryId: IdTuple = [trashSubfolderEntriesId, trashSubfolderMailSetEntryElementId]
const storableCustomFolder = await toStorableInstance(
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", trashSubfolderId],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -1345,7 +1345,7 @@ o.spec("OfflineStorageDb", function () {
folderType: MailSetKind.CUSTOM,
}),
)
await storage.put(MailFolderTypeRef, storableCustomFolder)
await storage.put(MailSetTypeRef, storableCustomFolder)
const storableSpamMailSetEntry = await toStorableInstance(
createTestEntity(MailSetEntryTypeRef, {
@ -1455,7 +1455,7 @@ o.spec("OfflineStorageDb", function () {
ensureBase64Ext(detailsBlobTypeModel, elementIdPart(trashSubfolderDetailsId)),
])
o.check(await getAllIdsForType(MailFolderTypeRef)).deepEquals([spamFolderId, trashFolderId, trashSubfolderId])
o.check(await getAllIdsForType(MailSetTypeRef)).deepEquals([spamFolderId, trashFolderId, trashSubfolderId])
const count = await dbFacade.get("SELECT COUNT(*) FROM list_entities", [])
o.check(untagSqlObject(assertNotNull(count))["COUNT(*)"]).equals(9)
})
@ -1532,14 +1532,14 @@ o.spec("OfflineStorageDb", function () {
}),
})
const inboxFolder = createTestEntity(MailFolderTypeRef, {
const inboxFolder = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", inboxFolderId],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
folderType: MailSetKind.INBOX,
entries: inboxFolderEntriesId,
})
await storage.put(MailFolderTypeRef, await toStorableInstance(inboxFolder))
await storage.put(MailSetTypeRef, await toStorableInstance(inboxFolder))
await storage.put(MailTypeRef, await toStorableInstance(mailBefore))
await storage.put(MailTypeRef, await toStorableInstance(mailAfter))
await storage.put(MailSetEntryTypeRef, await toStorableInstance(mailSetEntryBefore))
@ -1551,7 +1551,7 @@ o.spec("OfflineStorageDb", function () {
await storage.clearExcludedData(timeRangeDate, userId)
const mailSetEntryTypeModel = await typeModelResolver.resolveClientTypeReference(MailSetEntryTypeRef)
o.check(await getAllIdsForType(MailFolderTypeRef)).deepEquals([inboxFolderId, spamFolderId, trashFolderId])
o.check(await getAllIdsForType(MailSetTypeRef)).deepEquals([inboxFolderId, spamFolderId, trashFolderId])
const allMailSetEntryIds = await getAllIdsForType(MailSetEntryTypeRef)
o.check(allMailSetEntryIds).deepEquals([ensureBase64Ext(mailSetEntryTypeModel, twoDaysAfterMailSetEntryElementId)])
o.check(await getAllIdsForType(MailTypeRef)).deepEquals([twoDaysAfterMailId])
@ -1631,14 +1631,14 @@ o.spec("OfflineStorageDb", function () {
}),
})
const inboxFolder = createTestEntity(MailFolderTypeRef, {
const inboxFolder = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", inboxFolderId],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
folderType: MailSetKind.INBOX,
entries: inboxFolderEntriesId,
})
await storage.put(MailFolderTypeRef, await toStorableInstance(inboxFolder))
await storage.put(MailSetTypeRef, await toStorableInstance(inboxFolder))
await storage.put(MailTypeRef, await toStorableInstance(mailOneDayBefore))
await storage.put(MailTypeRef, await toStorableInstance(mailTwoDaysBefore))
await storage.put(MailSetEntryTypeRef, await toStorableInstance(mailSetEntryTwoDaysBefore))
@ -1649,7 +1649,7 @@ o.spec("OfflineStorageDb", function () {
// Here we clear the excluded data
await storage.clearExcludedData(timeRangeDate, userId)
o.check(await getAllIdsForType(MailFolderTypeRef)).deepEquals([inboxFolderId, spamFolderId, trashFolderId])
o.check(await getAllIdsForType(MailSetTypeRef)).deepEquals([inboxFolderId, spamFolderId, trashFolderId])
const allMailSetEntryIds = await getAllIdsForType(MailSetEntryTypeRef)
o.check(allMailSetEntryIds).deepEquals([])
o.check(await getAllIdsForType(MailTypeRef)).deepEquals([])
@ -1742,14 +1742,14 @@ o.spec("OfflineStorageDb", function () {
}),
})
const inboxFolder = createTestEntity(MailFolderTypeRef, {
const inboxFolder = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", inboxFolderId],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
folderType: MailSetKind.INBOX,
entries: inboxFolderEntriesId,
})
await storage.put(MailFolderTypeRef, await toStorableInstance(inboxFolder))
await storage.put(MailSetTypeRef, await toStorableInstance(inboxFolder))
await storage.put(MailSetEntryTypeRef, await toStorableInstance(mailSetEntryBefore))
await storage.put(MailSetEntryTypeRef, await toStorableInstance(mailSetEntryAfter))
await storage.put(MailTypeRef, await toStorableInstance(mailBefore))
@ -1777,7 +1777,7 @@ o.spec("OfflineStorageDb", function () {
mailSetEntryIdGenerator: MailSetEntryIdGenerator,
getSubject: (i: number) => string,
getBody: (i: number) => string,
folder: MailFolder,
folder: MailSet,
): { mailSetEntries: Array<MailSetEntry>; mails: Array<Mail>; mailDetailsBlobs: Array<MailDetailsBlob> } {
const mailSetEntries: Array<MailSetEntry> = []
const mails: Array<Mail> = []
@ -1844,7 +1844,7 @@ o.spec("OfflineStorageDb", function () {
mailImportStates: "mailImportStates",
})
const inboxFolder = createTestEntity(MailFolderTypeRef, {
const inboxFolder = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", oldIds.getNext()],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -1877,7 +1877,7 @@ o.spec("OfflineStorageDb", function () {
inboxFolder,
)
const trashFolder = createTestEntity(MailFolderTypeRef, {
const trashFolder = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", oldIds.getNext()],
_ownerGroup: "ownerGroup",
_permissions: "permissions",
@ -1909,7 +1909,7 @@ o.spec("OfflineStorageDb", function () {
trashFolder,
)
const spamFolder = createTestEntity(MailFolderTypeRef, {
const spamFolder = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", oldIds.getNext()],
_ownerGroup: "ownerGroup",
_permissions: "permissions",

View file

@ -29,6 +29,7 @@ import {
Mail,
MailAddress,
MailAddressTypeRef,
MailBox,
MailboxGroupRoot,
MailboxGroupRootTypeRef,
MailDetailsBlob,

View file

@ -20,9 +20,9 @@ import {
MailDetailsBlobTypeRef,
MailDetailsDraftTypeRef,
MailDetailsTypeRef,
MailFolder,
MailFolderRefTypeRef,
MailFolderTypeRef,
MailSet,
MailSetRefTypeRef,
MailSetTypeRef,
MailSetEntry,
MailSetEntryTypeRef,
MailTypeRef,
@ -172,9 +172,9 @@ o.spec("MailIndexer", () => {
})
})
function _addFolder(mailbox: MailBox): MailFolder {
const folder = createTestEntity(MailFolderTypeRef)
folder._id = [neverNull(mailbox.folders).folders, entityMock.getNextId()]
function _addFolder(mailbox: MailBox): MailSet {
const folder = createTestEntity(MailSetTypeRef)
folder._id = [mailbox.folders.folders, entityMock.getNextId()]
folder.entries = entityMock.getNextId()
return folder
}
@ -193,7 +193,7 @@ o.spec("MailIndexer", () => {
const rangeEndShifted2Days = getDayShifted(new Date(rangeEnd), -2).getTime()
const mailGroup = "mail-group-id"
let mailbox: MailBox
let folder1: MailFolder, folder2: MailFolder
let folder1: MailSet, folder2: MailSet
let mail0: Mail,
details0: MailDetailsBlob,
mailEntry0: MailSetEntry,
@ -217,7 +217,7 @@ o.spec("MailIndexer", () => {
mailbox.currentMailBag = createTestEntity(MailBagTypeRef, { _id: "mailBagId", mails: mailBagMailListId })
mailbox._id = "mailbox-id"
mailbox._ownerGroup = mailGroup
const folderRef = createTestEntity(MailFolderRefTypeRef)
const folderRef = createTestEntity(MailSetRefTypeRef)
folderRef.folders = entityMock.getNextId()
mailbox.folders = folderRef
folder1 = _addFolder(mailbox)
@ -768,7 +768,7 @@ o.spec("MailIndexer", () => {
const mailbox = createTestEntity(MailBoxTypeRef, {
_id: "mailbox-id",
_ownerGroup: mailGroup1,
folders: createTestEntity(MailFolderRefTypeRef, {
folders: createTestEntity(MailSetRefTypeRef, {
folders: "foldersId",
}),
})

View file

@ -15,8 +15,8 @@ import {
MailBoxTypeRef,
MailDetails,
MailDetailsTypeRef,
MailFolderRefTypeRef,
MailFolderTypeRef,
MailSetRefTypeRef,
MailSetTypeRef,
MailTypeRef,
} from "../../../../../../src/common/api/entities/tutanota/TypeRefs"
import { MailSetKind, MAX_NBR_OF_MAILS_SYNC_OPERATION, SpamDecision } from "../../../../../../src/common/api/common/TutanotaConstants"
@ -67,17 +67,17 @@ o.spec("SpamClassificationDataDealer", () => {
let mailboxGroupRoot: MailboxGroupRoot
let mailBox: MailBox
const inboxFolder = createTestEntity(MailFolderTypeRef, {
const inboxFolder = createTestEntity(MailSetTypeRef, {
_id: ["folderListId", "inbox"],
_ownerGroup: "owner",
folderType: MailSetKind.INBOX,
})
const trashFolder = createTestEntity(MailFolderTypeRef, {
const trashFolder = createTestEntity(MailSetTypeRef, {
_id: ["folderListId", "trash"],
_ownerGroup: "owner",
folderType: MailSetKind.TRASH,
})
const spamFolder = createTestEntity(MailFolderTypeRef, {
const spamFolder = createTestEntity(MailSetTypeRef, {
_id: ["folderListId", "spam"],
_ownerGroup: "owner",
folderType: MailSetKind.SPAM,
@ -91,7 +91,7 @@ o.spec("SpamClassificationDataDealer", () => {
mailBox = createTestEntity(MailBoxTypeRef, {
_id: "mailbox",
_ownerGroup: "owner",
folders: createTestEntity(MailFolderRefTypeRef, { folders: "folderListId" }),
folders: createTestEntity(MailSetRefTypeRef, { folders: "folderListId" }),
currentMailBag: createTestEntity(MailBagTypeRef, { mails: "mailListId" }),
archivedMailBags: [createTestEntity(MailBagTypeRef, { mails: "oldMailListId" })],
clientSpamTrainingData: "clientSpamTrainingData",
@ -141,20 +141,6 @@ o.spec("SpamClassificationDataDealer", () => {
})
o.spec("fetchAllTrainingData", () => {
o("returns empty training data when index or training data is null", async () => {
mailBox.clientSpamTrainingData = null
mailBox.modifiedClientSpamTrainingDataIndex = null
when(entityClientMock.load(MailboxGroupRootTypeRef, "owner")).thenResolve(mailboxGroupRoot)
when(entityClientMock.load(MailBoxTypeRef, "mailbox")).thenResolve(mailBox)
const trainingDataset = await spamClassificationDataDealer.fetchAllTrainingData("owner")
o(trainingDataset.trainingData.length).equals(0)
o(trainingDataset.hamCount).equals(0)
o(trainingDataset.spamCount).equals(0)
o(trainingDataset.lastTrainingDataIndexId).equals(GENERATED_MIN_ID)
})
o("uploads training data when clientSpamTrainingData is empty", async () => {
when(entityClientMock.load(MailboxGroupRootTypeRef, "owner")).thenResolve(mailboxGroupRoot)
when(entityClientMock.load(MailBoxTypeRef, "mailbox")).thenResolve(mailBox)
@ -167,13 +153,13 @@ o.spec("SpamClassificationDataDealer", () => {
)
const spamTrainingData = Array.from({ length: 10 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.WHITELIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(mails[index]),
]),
).concat(
Array.from({ length: 10 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.BLACKLIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(mails[10 + index]),
]),
),
@ -181,11 +167,11 @@ o.spec("SpamClassificationDataDealer", () => {
const modifiedIndicesSinceStart = spamTrainingData.map((data) =>
createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId(getElementId(data)),
)
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!)).thenResolve([], spamTrainingData)
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData)).thenResolve([], spamTrainingData)
when(entityClientMock.loadAll(MailTypeRef, mailBox.currentMailBag!.mails, anything())).thenResolve(mails)
when(entityClientMock.loadAll(MailTypeRef, mailBox.archivedMailBags[0].mails, anything())).thenResolve([])
when(entityClientMock.loadAll(MailFolderTypeRef, mailBox.folders!.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!)).thenResolve(
when(entityClientMock.loadAll(MailSetTypeRef, mailBox.folders.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex)).thenResolve(
modifiedIndicesSinceStart,
)
@ -198,8 +184,8 @@ o.spec("SpamClassificationDataDealer", () => {
const trainingDataset = await spamClassificationDataDealer.fetchAllTrainingData("owner")
// first load: empty, second load: fetch uploaded data
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!), { times: 2 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!), { times: 1 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData), { times: 2 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex), { times: 1 })
const unencryptedPayload = mails.map((mail) => {
return {
mailId: mail._id,
@ -232,13 +218,13 @@ o.spec("SpamClassificationDataDealer", () => {
const existingSpamTrainingData = Array.from({ length: 20 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.WHITELIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[index]),
]),
).concat(
Array.from({ length: 20 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.BLACKLIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[40 + index]),
]),
),
@ -246,13 +232,13 @@ o.spec("SpamClassificationDataDealer", () => {
const updatedSpamTrainingData = Array.from({ length: 40 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.WHITELIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[index]),
]),
).concat(
Array.from({ length: 40 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.BLACKLIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[40 + index]),
]),
),
@ -262,14 +248,14 @@ o.spec("SpamClassificationDataDealer", () => {
createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId(getElementId(data)),
)
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!)).thenResolve(
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData)).thenResolve(
existingSpamTrainingData,
updatedSpamTrainingData,
)
when(entityClientMock.loadAll(MailTypeRef, mailBox.currentMailBag!.mails, anything())).thenResolve(relevantMails)
when(entityClientMock.loadAll(MailTypeRef, mailBox.archivedMailBags[0].mails, anything())).thenResolve([])
when(entityClientMock.loadAll(MailFolderTypeRef, mailBox.folders!.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!)).thenResolve(
when(entityClientMock.loadAll(MailSetTypeRef, mailBox.folders.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex)).thenResolve(
modifiedIndicesSinceStart,
)
@ -286,8 +272,8 @@ o.spec("SpamClassificationDataDealer", () => {
const trainingDataset = await spamClassificationDataDealer.fetchAllTrainingData("owner")
// first load: empty, second load: fetch uploaded data
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!), { times: 2 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!), { times: 1 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData), { times: 2 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex), { times: 1 })
const unencryptedPayload = expectUploadMailsTotal.map((mail) => {
return {
@ -321,13 +307,13 @@ o.spec("SpamClassificationDataDealer", () => {
const existingSpamTrainingData = Array.from({ length: 40 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.WHITELIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[index]),
]),
).concat(
Array.from({ length: 40 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.BLACKLIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[80 + index]),
]),
),
@ -335,13 +321,13 @@ o.spec("SpamClassificationDataDealer", () => {
const updatedSpamTrainingData = Array.from({ length: 80 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.WHITELIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[index]),
]),
).concat(
Array.from({ length: 80 }, (_, index) =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.BLACKLIST, [
mailBox.clientSpamTrainingData!,
mailBox.clientSpamTrainingData,
getElementId(relevantMails[80 + index]),
]),
),
@ -351,14 +337,14 @@ o.spec("SpamClassificationDataDealer", () => {
createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId(getElementId(data)),
)
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!)).thenResolve(
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData)).thenResolve(
existingSpamTrainingData,
updatedSpamTrainingData,
)
when(entityClientMock.loadAll(MailTypeRef, mailBox.currentMailBag!.mails, anything())).thenResolve(relevantMails)
when(entityClientMock.loadAll(MailTypeRef, mailBox.archivedMailBags[0].mails, anything())).thenResolve([])
when(entityClientMock.loadAll(MailFolderTypeRef, mailBox.folders!.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!)).thenResolve(
when(entityClientMock.loadAll(MailSetTypeRef, mailBox.folders.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex)).thenResolve(
modifiedIndicesSinceStart,
)
@ -382,8 +368,8 @@ o.spec("SpamClassificationDataDealer", () => {
const trainingDataset = await spamClassificationDataDealer.fetchAllTrainingData("owner")
// first load: empty, second load: fetch uploaded data
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!), { times: 2 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!), { times: 1 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData), { times: 2 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex), { times: 1 })
const firstUnencryptedPayload = expectedFirstChunk.map((mail) => {
return {
@ -423,17 +409,17 @@ o.spec("SpamClassificationDataDealer", () => {
const modifiedIndicesSinceStart = spamTrainingData.map((data) =>
createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId(getElementId(data)),
)
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!)).thenResolve(spamTrainingData)
when(entityClientMock.loadAll(MailFolderTypeRef, mailBox.folders!.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!)).thenResolve(
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData)).thenResolve(spamTrainingData)
when(entityClientMock.loadAll(MailSetTypeRef, mailBox.folders.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex)).thenResolve(
modifiedIndicesSinceStart,
)
const trainingDataset = await spamClassificationDataDealer.fetchAllTrainingData("owner")
// only one load as the list is already populated
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!), { times: 1 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!), { times: 1 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData), { times: 1 })
verify(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex), { times: 1 })
o(trainingDataset).deepEquals({
trainingData: spamTrainingData,
@ -456,13 +442,13 @@ o.spec("SpamClassificationDataDealer", () => {
const modifiedIndicesSinceStart = spamTrainingData.map((data) =>
createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId(getElementId(data)),
)
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData!)).thenResolve(spamTrainingData)
when(entityClientMock.loadAll(ClientSpamTrainingDatumTypeRef, mailBox.clientSpamTrainingData)).thenResolve(spamTrainingData)
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex!)).thenResolve(
when(entityClientMock.loadAll(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex)).thenResolve(
modifiedIndicesSinceStart,
)
when(entityClientMock.loadAll(MailFolderTypeRef, mailBox.folders!.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
when(entityClientMock.loadAll(MailSetTypeRef, mailBox.folders.folders)).thenResolve([inboxFolder, spamFolder, trashFolder])
const result = await spamClassificationDataDealer.fetchAllTrainingData("owner")
@ -474,27 +460,13 @@ o.spec("SpamClassificationDataDealer", () => {
})
o.spec("fetchPartialTrainingDataFromIndexStartId", () => {
o("returns empty training data when index or training data is null", async () => {
mailBox.clientSpamTrainingData = null
mailBox.modifiedClientSpamTrainingDataIndex = null
when(entityClientMock.load(MailboxGroupRootTypeRef, "owner")).thenResolve(mailboxGroupRoot)
when(entityClientMock.load(MailBoxTypeRef, "mailbox")).thenResolve(mailBox)
const trainingDataset = await spamClassificationDataDealer.fetchPartialTrainingDataFromIndexStartId("startId", "owner")
o(trainingDataset.trainingData.length).equals(0)
o(trainingDataset.hamCount).equals(0)
o(trainingDataset.spamCount).equals(0)
o(trainingDataset.lastTrainingDataIndexId).equals("startId")
})
o("returns empty training data when modifiedClientSpamTrainingDataIndicesSinceStart are null", async () => {
when(entityClientMock.load(MailboxGroupRootTypeRef, "owner")).thenResolve(mailboxGroupRoot)
when(entityClientMock.load(MailBoxTypeRef, "mailbox")).thenResolve(mailBox)
when(
entityClientMock.loadRange(
ClientSpamTrainingDatumIndexEntryTypeRef,
mailBox.modifiedClientSpamTrainingDataIndex!,
mailBox.modifiedClientSpamTrainingDataIndex,
"startId",
SINGLE_TRAIN_INTERVAL_TRAINING_DATA_LIMIT,
false,
@ -517,26 +489,20 @@ o.spec("SpamClassificationDataDealer", () => {
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.WHITELIST),
).concat(Array.from({ length: 50 }, () => createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.BLACKLIST)))
oldSpamTrainingData.map((data) => (data._id = [mailBox.clientSpamTrainingData!, GENERATED_MIN_ID]))
oldSpamTrainingData.map((data) => (data._id = [mailBox.clientSpamTrainingData, GENERATED_MIN_ID]))
const newSpamTrainingData = Array.from({ length: 10 }, () =>
createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.WHITELIST),
).concat(Array.from({ length: 10 }, () => createSpamTrainingDatumByConfidenceAndDecision(DEFAULT_IS_SPAM_CONFIDENCE, SpamDecision.BLACKLIST)))
newSpamTrainingData.map((data) => (data._id = [mailBox.clientSpamTrainingData!, GENERATED_MIN_ID]))
newSpamTrainingData.map((data) => (data._id = [mailBox.clientSpamTrainingData, GENERATED_MIN_ID]))
const modifiedIndicesSinceStart = newSpamTrainingData.map((data) =>
createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId(getElementId(data)),
)
when(
entityClientMock.loadRange(
ClientSpamTrainingDatumIndexEntryTypeRef,
mailBox.modifiedClientSpamTrainingDataIndex!,
"startId",
anything(),
false,
),
entityClientMock.loadRange(ClientSpamTrainingDatumIndexEntryTypeRef, mailBox.modifiedClientSpamTrainingDataIndex, "startId", anything(), false),
).thenResolve(modifiedIndicesSinceStart)
when(

View file

@ -10,7 +10,7 @@ import {
MailDetailsBlob,
MailDetailsBlobTypeRef,
MailDetailsTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
MailTypeRef,
RecipientsTypeRef,
} from "../../../src/common/api/entities/tutanota/TypeRefs.js"
@ -39,15 +39,15 @@ o.spec("MailModelTest", function () {
let notifications: Partial<Notifications>
let showSpy: Spy
let model: MailModel
const inboxFolder = createTestEntity(MailFolderTypeRef, {
const inboxFolder = createTestEntity(MailSetTypeRef, {
_id: ["folderListId", "inboxId"],
folderType: MailSetKind.INBOX,
})
const spamFolder = createTestEntity(MailFolderTypeRef, {
const spamFolder = createTestEntity(MailSetTypeRef, {
_id: ["folderListId", "spamId"],
folderType: MailSetKind.SPAM,
})
const anotherFolder = createTestEntity(MailFolderTypeRef, {
const anotherFolder = createTestEntity(MailSetTypeRef, {
_id: ["folderListId", "archiveId"],
folderType: MailSetKind.ARCHIVE,
})

View file

@ -7,7 +7,7 @@ import {
Mail,
MailDetails,
MailDetailsTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
MailTypeRef,
} from "../../../src/common/api/entities/tutanota/TypeRefs"
import { FeatureType, MailSetKind, ProcessingState, SpamDecision } from "../../../src/common/api/common/TutanotaConstants"
@ -38,9 +38,9 @@ o.spec("ProcessInboxHandlerTest", function () {
let inboxRuleHandler: InboxRuleHandler = object<InboxRuleHandler>()
let processInboxHandler: ProcessInboxHandler
const inboxFolder = createTestEntity(MailFolderTypeRef, { _id: ["listId", "inbox"], folderType: MailSetKind.INBOX })
const trashFolder = createTestEntity(MailFolderTypeRef, { _id: ["listId", "trash"], folderType: MailSetKind.TRASH })
const spamFolder = createTestEntity(MailFolderTypeRef, { _id: ["listId", "spam"], folderType: MailSetKind.SPAM })
const inboxFolder = createTestEntity(MailSetTypeRef, { _id: ["listId", "inbox"], folderType: MailSetKind.INBOX })
const trashFolder = createTestEntity(MailSetTypeRef, { _id: ["listId", "trash"], folderType: MailSetKind.TRASH })
const spamFolder = createTestEntity(MailSetTypeRef, { _id: ["listId", "spam"], folderType: MailSetKind.SPAM })
o.beforeEach(function () {
spamHandler = object<SpamClassificationHandler>()

View file

@ -7,7 +7,7 @@ import {
Mail,
MailDetails,
MailDetailsTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
MailTypeRef,
} from "../../../src/common/api/entities/tutanota/TypeRefs"
import { SpamClassifier } from "../../../src/mail-app/workerUtils/spamClassification/SpamClassifier"
@ -34,9 +34,9 @@ o.spec("SpamClassificationHandlerTest", function () {
let folderSystem: FolderSystem
let mailDetails: MailDetails
const inboxFolder = createTestEntity(MailFolderTypeRef, { _id: ["listId", "inbox"], folderType: MailSetKind.INBOX })
const trashFolder = createTestEntity(MailFolderTypeRef, { _id: ["listId", "trash"], folderType: MailSetKind.TRASH })
const spamFolder = createTestEntity(MailFolderTypeRef, { _id: ["listId", "spam"], folderType: MailSetKind.SPAM })
const inboxFolder = createTestEntity(MailSetTypeRef, { _id: ["listId", "inbox"], folderType: MailSetKind.INBOX })
const trashFolder = createTestEntity(MailSetTypeRef, { _id: ["listId", "trash"], folderType: MailSetKind.TRASH })
const spamFolder = createTestEntity(MailSetTypeRef, { _id: ["listId", "spam"], folderType: MailSetKind.SPAM })
const compressedUnencryptedTestVector = new Uint8Array([23, 3, 21, 12, 14, 2, 23, 3, 30, 3, 4, 3, 2, 31, 23, 22, 30])

View file

@ -4,8 +4,8 @@ import {
Mail,
MailboxGroupRootTypeRef,
MailBoxTypeRef,
MailFolder,
MailFolderTypeRef,
MailSet,
MailSetTypeRef,
MailSetEntry,
MailSetEntryTypeRef,
MailTypeRef,
@ -62,15 +62,15 @@ o.spec("ConversationListModel", () => {
const mailSetEntriesListId = "entries"
const _ownerGroup = "me"
const labels: MailFolder[] = [
createTestEntity(MailFolderTypeRef, {
const labels: MailSet[] = [
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "tutaPrimary"],
color: theme.primary,
folderType: MailSetKind.LABEL,
name: "Tuta Primary Label",
parentFolder: null,
}),
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "tutaSecondary"],
color: theme.secondary,
folderType: MailSetKind.LABEL,
@ -79,7 +79,7 @@ o.spec("ConversationListModel", () => {
}),
]
let mailSet: MailFolder
let mailSet: MailSet
let conversationPrefProvider: ConversationPrefProvider
let entityClient: EntityClient
let mailModel: MailModel
@ -88,7 +88,7 @@ o.spec("ConversationListModel", () => {
let connectivityModel: WebsocketConnectivityModel
o.beforeEach(() => {
mailSet = createTestEntity(MailFolderTypeRef, {
mailSet = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "mailFolderId"],
folderType: MailSetKind.CUSTOM,
name: "My Folder",
@ -125,7 +125,7 @@ o.spec("ConversationListModel", () => {
}
// Creates a totalMails number of mails, grouping into a number of conversations equal to totalMails/mailsPerConversation
async function setUpTestData(totalMails: number, initialLabels: MailFolder[], offline: boolean, mailsPerConversation: number): Promise<Mail[]> {
async function setUpTestData(totalMails: number, initialLabels: MailSet[], offline: boolean, mailsPerConversation: number): Promise<Mail[]> {
const mailSetEntries: MailSetEntry[] = []
const mails: Mail[][] = [[], [], [], [], [], [], [], [], [], []]
const allMails: Mail[] = []
@ -155,7 +155,7 @@ o.spec("ConversationListModel", () => {
}
when(mailModel.getLabelsForMail(matchers.anything())).thenDo((mail: Mail) => {
const sets: MailFolder[] = []
const sets: MailSet[] = []
for (const set of mail.sets) {
const setToAdd = labels.find((label) => isSameId(label._id, set))
if (setToAdd) {
@ -436,7 +436,7 @@ o.spec("ConversationListModel", () => {
o.check(model.getLabelsForMail(someMail.mail)[1]).notDeepEquals(labels[1])
const entityUpdateData: EntityUpdateData = {
typeRef: MailFolderTypeRef,
typeRef: MailSetTypeRef,
instanceListId: getListId(labels[1]) as NonEmptyString,
instanceId: getElementId(labels[1]),
operation: OperationType.DELETE,
@ -464,7 +464,7 @@ o.spec("ConversationListModel", () => {
await model.loadInitial()
const entityUpdateData: EntityUpdateData = {
typeRef: MailFolderTypeRef,
typeRef: MailSetTypeRef,
instanceListId: getListId(labels[1]) as NonEmptyString,
instanceId: getElementId(labels[1]),
operation: OperationType.DELETE,
@ -516,7 +516,7 @@ o.spec("ConversationListModel", () => {
mail: Mail
mailSetEntry: MailSetEntry
entityUpdateData: EntityUpdateData
mailLabels: MailFolder[]
mailLabels: MailSet[]
} {
const newMail = createTestEntity(MailTypeRef, {
_id: ["new mail!!!", deconstructMailSetEntryId(elementIdPart(mailSetEntryId)).mailId],

View file

@ -1,5 +1,5 @@
import o from "@tutao/otest"
import { MailFolderTypeRef, MailTypeRef } from "../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { MailSetTypeRef, MailTypeRef } from "../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { MailSetKind } from "../../../../src/common/api/common/TutanotaConstants.js"
import { FolderSystem } from "../../../../src/common/api/common/mail/FolderSystem.js"
import { createTestEntity } from "../../TestUtils.js"
@ -7,26 +7,26 @@ import { getElementId } from "../../../../src/common/api/common/utils/EntityUtil
o.spec("FolderSystem", function () {
const listId = "listId"
const inbox = createTestEntity(MailFolderTypeRef, { _id: [listId, "inbox"], folderType: MailSetKind.INBOX })
const archive = createTestEntity(MailFolderTypeRef, { _id: [listId, "archive"], folderType: MailSetKind.ARCHIVE })
const customFolder = createTestEntity(MailFolderTypeRef, {
const inbox = createTestEntity(MailSetTypeRef, { _id: [listId, "inbox"], folderType: MailSetKind.INBOX })
const archive = createTestEntity(MailSetTypeRef, { _id: [listId, "archive"], folderType: MailSetKind.ARCHIVE })
const customFolder = createTestEntity(MailSetTypeRef, {
_id: [listId, "custom"],
folderType: MailSetKind.CUSTOM,
name: "X",
})
const customSubfolder = createTestEntity(MailFolderTypeRef, {
const customSubfolder = createTestEntity(MailSetTypeRef, {
_id: [listId, "customSub"],
folderType: MailSetKind.CUSTOM,
parentFolder: customFolder._id,
name: "AA",
})
const customSubSubfolder = createTestEntity(MailFolderTypeRef, {
const customSubSubfolder = createTestEntity(MailSetTypeRef, {
_id: [listId, "customSubSub"],
folderType: MailSetKind.CUSTOM,
parentFolder: customSubfolder._id,
name: "B",
})
const customSubSubfolderAnother = createTestEntity(MailFolderTypeRef, {
const customSubSubfolderAnother = createTestEntity(MailSetTypeRef, {
_id: [listId, "customSubSubAnother"],
folderType: MailSetKind.CUSTOM,
parentFolder: customSubfolder._id,
@ -74,12 +74,12 @@ o.spec("FolderSystem", function () {
})
o("indented list sorts stepsiblings correctly", function () {
const customFolderAnother = createTestEntity(MailFolderTypeRef, {
const customFolderAnother = createTestEntity(MailSetTypeRef, {
_id: [listId, "customAnother"],
folderType: MailSetKind.CUSTOM,
name: "Another top-level custom",
})
const customFolderAnotherSub = createTestEntity(MailFolderTypeRef, {
const customFolderAnotherSub = createTestEntity(MailSetTypeRef, {
_id: [listId, "customAnotherSub"],
folderType: MailSetKind.CUSTOM,
parentFolder: customFolderAnother._id,

View file

@ -5,8 +5,8 @@ import {
Mail,
MailboxGroupRootTypeRef,
MailBoxTypeRef,
MailFolder,
MailFolderTypeRef,
MailSet,
MailSetTypeRef,
MailSetEntry,
MailSetEntryTypeRef,
MailTypeRef,
@ -59,15 +59,15 @@ o.spec("MailListModel", () => {
const mailSetEntriesListId = "entries"
const _ownerGroup = "me"
const labels: MailFolder[] = [
createTestEntity(MailFolderTypeRef, {
const labels: MailSet[] = [
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "tutaPrimary"],
color: theme.primary,
folderType: MailSetKind.LABEL,
name: "Tuta Primary Label",
parentFolder: null,
}),
createTestEntity(MailFolderTypeRef, {
createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "tutaSecondary"],
color: theme.secondary,
folderType: MailSetKind.LABEL,
@ -76,7 +76,7 @@ o.spec("MailListModel", () => {
}),
]
let mailSet: MailFolder
let mailSet: MailSet
let conversationPrefProvider: ConversationPrefProvider
let entityClient: EntityClient
let mailModel: MailModel
@ -85,7 +85,7 @@ o.spec("MailListModel", () => {
let connectivityModel: WebsocketConnectivityModel
o.beforeEach(() => {
mailSet = createTestEntity(MailFolderTypeRef, {
mailSet = createTestEntity(MailSetTypeRef, {
_id: ["mailFolderList", "mailFolderId"],
folderType: MailSetKind.CUSTOM,
name: "My Folder",
@ -123,7 +123,7 @@ o.spec("MailListModel", () => {
async function setUpTestData(
count: number,
initialLabels: MailFolder[],
initialLabels: MailSet[],
offline: boolean,
mailTemplate: (idx: number) => Mail = (idx) =>
createTestEntity(MailTypeRef, {
@ -151,7 +151,7 @@ o.spec("MailListModel", () => {
}
when(mailModel.getLabelsForMail(matchers.anything())).thenDo((mail: Mail) => {
const sets: MailFolder[] = []
const sets: MailSet[] = []
for (const set of mail.sets) {
const setToAdd = labels.find((label) => isSameId(label._id, set))
if (setToAdd) {
@ -370,7 +370,7 @@ o.spec("MailListModel", () => {
o(model.getLabelsForMail(someMail.mail)[1]).notDeepEquals(labels[1])
const entityUpdateData: EntityUpdateData = {
typeRef: MailFolderTypeRef,
typeRef: MailSetTypeRef,
instanceListId: getListId(labels[1]) as NonEmptyString,
instanceId: getElementId(labels[1]),
operation: OperationType.DELETE,
@ -392,7 +392,7 @@ o.spec("MailListModel", () => {
await model.loadInitial()
const entityUpdateData: EntityUpdateData = {
typeRef: MailFolderTypeRef,
typeRef: MailSetTypeRef,
instanceListId: getListId(labels[1]) as NonEmptyString,
instanceId: getElementId(labels[1]),
operation: OperationType.DELETE,
@ -434,7 +434,7 @@ o.spec("MailListModel", () => {
mail: Mail
mailSetEntry: MailSetEntry
entityUpdateData: EntityUpdateData
mailLabels: MailFolder[]
mailLabels: MailSet[]
} {
const newMail = createTestEntity(MailTypeRef, {
_id: ["new mail!!!", "the mail!!!"],

View file

@ -6,7 +6,7 @@ import {
Mail,
MailboxProperties,
MailboxPropertiesTypeRef,
MailFolderTypeRef,
MailSetTypeRef,
MailTypeRef,
} from "../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { CreateMailViewerOptions } from "../../../../src/mail-app/mail/view/MailViewer.js"
@ -198,7 +198,7 @@ o.spec("ConversationViewModel", function () {
const trashDraftMail = addMail("trashDraftMail")
trashDraftMail.state = MailState.DRAFT
const trash = createTestEntity(MailFolderTypeRef, {
const trash = createTestEntity(MailSetTypeRef, {
_id: [listId, "trashFolder"],
folderType: MailSetKind.TRASH,
})
@ -223,7 +223,7 @@ o.spec("ConversationViewModel", function () {
const trashDraftMail = addMail("trashDraftMail")
trashDraftMail.state = MailState.DRAFT
const trash = createTestEntity(MailFolderTypeRef, {
const trash = createTestEntity(MailSetTypeRef, {
_id: [listId, "trashFolder"],
folderType: MailSetKind.TRASH,
})
@ -371,7 +371,7 @@ o.spec("ConversationViewModel", function () {
await loadingDefer.promise
conversation.pop()
const trash = createTestEntity(MailFolderTypeRef, {
const trash = createTestEntity(MailSetTypeRef, {
_id: ["folderListId", "trashFolder"],
folderType: MailSetKind.TRASH,
})

View file

@ -1,6 +1,6 @@
import o from "@tutao/otest"
import { createTestEntity } from "../../TestUtils"
import { MailFolderTypeRef } from "../../../../src/common/api/entities/tutanota/TypeRefs"
import { MailSetTypeRef } from "../../../../src/common/api/entities/tutanota/TypeRefs"
import { MailSetKind } from "../../../../src/common/api/common/TutanotaConstants"
import { ConversationPrefProvider } from "../../../../src/mail-app/mail/view/ConversationViewModel"
import { object, when } from "testdouble"
@ -10,7 +10,7 @@ import { listByConversationInFolder } from "../../../../src/mail-app/mail/view/M
o.spec("MailViewModelTest", () => {
o.spec("listByConversation", () => {
o.spec("in inbox folder", () => {
const testInbox = createTestEntity(MailFolderTypeRef, {
const testInbox = createTestEntity(MailSetTypeRef, {
folderType: MailSetKind.INBOX,
})
@ -36,10 +36,10 @@ o.spec("MailViewModelTest", () => {
})
})
o.spec("in sent and draft folders", () => {
const testDraftFolder = createTestEntity(MailFolderTypeRef, {
const testDraftFolder = createTestEntity(MailSetTypeRef, {
folderType: MailSetKind.DRAFT,
})
const testSentFolder = createTestEntity(MailFolderTypeRef, {
const testSentFolder = createTestEntity(MailSetTypeRef, {
folderType: MailSetKind.SENT,
})

View file

@ -460,9 +460,9 @@ pub struct MailBox {
#[serde(rename = "134")]
pub receivedAttachments: GeneratedId,
#[serde(rename = "443")]
pub folders: Option<MailFolderRef>,
pub folders: MailSetRef,
#[serde(rename = "1220")]
pub spamResults: Option<SpamResults>,
pub spamResults: SpamResults,
#[serde(rename = "1318")]
pub mailDetailsDrafts: Option<MailDetailsDraftsRef>,
#[serde(rename = "1463")]
@ -474,11 +474,11 @@ pub struct MailBox {
#[serde(rename = "1585")]
pub mailImportStates: GeneratedId,
#[serde(rename = "1710")]
pub extractedFeatures: Option<GeneratedId>,
pub extractedFeatures: GeneratedId,
#[serde(rename = "1754")]
pub clientSpamTrainingData: Option<GeneratedId>,
pub clientSpamTrainingData: GeneratedId,
#[serde(rename = "1755")]
pub modifiedClientSpamTrainingDataIndex: Option<GeneratedId>,
pub modifiedClientSpamTrainingDataIndex: GeneratedId,
#[serde(default)]
pub _errors: Errors,
@ -815,7 +815,7 @@ impl Entity for DeleteMailData {
#[derive(uniffi::Record, Clone, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "testing"), derive(PartialEq, Debug))]
pub struct MailFolder {
pub struct MailSet {
#[serde(rename = "431")]
pub _id: Option<IdTupleGenerated>,
#[serde(rename = "432")]
@ -846,7 +846,7 @@ pub struct MailFolder {
pub _finalIvs: HashMap<String, Option<FinalIv>>,
}
impl Entity for MailFolder {
impl Entity for MailSet {
fn type_ref() -> TypeRef {
TypeRef {
app: AppName::Tutanota,
@ -857,14 +857,14 @@ impl Entity for MailFolder {
#[derive(uniffi::Record, Clone, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "testing"), derive(PartialEq, Debug))]
pub struct MailFolderRef {
pub struct MailSetRef {
#[serde(rename = "441")]
pub _id: Option<CustomId>,
#[serde(rename = "442")]
pub folders: GeneratedId,
}
impl Entity for MailFolderRef {
impl Entity for MailSetRef {
fn type_ref() -> TypeRef {
TypeRef {
app: AppName::Tutanota,
@ -1660,8 +1660,6 @@ pub struct MailboxServerProperties {
pub _format: i64,
#[serde(rename = "682")]
pub _ownerGroup: Option<GeneratedId>,
#[serde(rename = "683")]
pub whitelistProtectionEnabled: bool,
}
impl Entity for MailboxServerProperties {

View file

@ -1,9 +1,9 @@
use crate::entities::generated::tutanota::MailFolder;
use crate::entities::generated::tutanota::MailSet;
use num_enum::TryFromPrimitive;
pub struct FolderSystem {
// this structure should probably change rather soon
folders: Vec<MailFolder>,
folders: Vec<MailSet>,
}
#[derive(Copy, Clone, PartialEq, TryFromPrimitive, Debug)]
@ -20,7 +20,7 @@ pub enum MailSetKind {
Unknown = 9999,
}
impl MailFolder {
impl MailSet {
fn mail_set_kind(&self) -> MailSetKind {
MailSetKind::try_from(self.folderType as u64).unwrap_or(MailSetKind::Unknown)
}
@ -28,12 +28,12 @@ impl MailFolder {
impl FolderSystem {
#[must_use]
pub fn new(folders: Vec<MailFolder>) -> Self {
pub fn new(folders: Vec<MailSet>) -> Self {
Self { folders }
}
#[must_use]
pub fn system_folder_by_type(&self, mail_set_kind: MailSetKind) -> Option<&MailFolder> {
pub fn system_folder_by_type(&self, mail_set_kind: MailSetKind) -> Option<&MailSet> {
self.folders
.iter()
.find(|f| f.mail_set_kind() == mail_set_kind)

View file

@ -3,7 +3,7 @@ use crate::crypto_entity_client::CryptoEntityClient;
use crate::element_value::ParsedEntity;
use crate::entities::generated::sys::{Group, GroupInfo};
use crate::entities::generated::tutanota::{
Mail, MailBox, MailFolder, MailboxGroupRoot, SimpleMoveMailPostIn, UnreadMailStatePostIn,
Mail, MailBox, MailSet, MailboxGroupRoot, SimpleMoveMailPostIn, UnreadMailStatePostIn,
};
use crate::entities::Entity;
use crate::folder_system::{FolderSystem, MailSetKind};
@ -71,8 +71,8 @@ impl MailFacade {
&self,
mailbox: &MailBox,
) -> Result<FolderSystem, ApiCallError> {
let folders_list = &mailbox.folders.as_ref().unwrap().folders;
let folders: Vec<MailFolder> = self
let folders_list = &mailbox.folders.folders;
let folders: Vec<MailSet> = self
.crypto_entity_client
.load_range(
folders_list,

View file

@ -11,12 +11,12 @@ use crate::entities::generated::monitor::ReadCounterReturn;
use crate::entities::generated::monitor::ReportErrorIn;
pub struct CounterService;
crate::service_impl!(declare, CounterService, "monitor/counterservice", 36);
crate::service_impl!(declare, CounterService, "monitor/counterservice", 37);
crate::service_impl!(POST, CounterService, WriteCounterData, ());
crate::service_impl!(GET, CounterService, ReadCounterData, ReadCounterReturn);
pub struct ReportErrorService;
crate::service_impl!(declare, ReportErrorService, "monitor/reporterrorservice", 36);
crate::service_impl!(declare, ReportErrorService, "monitor/reporterrorservice", 37);
crate::service_impl!(POST, ReportErrorService, ReportErrorIn, ());

View file

@ -61,70 +61,70 @@ use crate::entities::generated::tutanota::UserAccountCreateData;
use crate::entities::generated::tutanota::UserAccountPostOut;
pub struct ApplyLabelService;
crate::service_impl!(declare, ApplyLabelService, "tutanota/applylabelservice", 98);
crate::service_impl!(declare, ApplyLabelService, "tutanota/applylabelservice", 99);
crate::service_impl!(POST, ApplyLabelService, ApplyLabelServicePostIn, ());
pub struct CalendarService;
crate::service_impl!(declare, CalendarService, "tutanota/calendarservice", 98);
crate::service_impl!(declare, CalendarService, "tutanota/calendarservice", 99);
crate::service_impl!(POST, CalendarService, UserAreaGroupPostData, CreateGroupPostReturn);
crate::service_impl!(DELETE, CalendarService, CalendarDeleteData, ());
pub struct ChangePrimaryAddressService;
crate::service_impl!(declare, ChangePrimaryAddressService, "tutanota/changeprimaryaddressservice", 98);
crate::service_impl!(declare, ChangePrimaryAddressService, "tutanota/changeprimaryaddressservice", 99);
crate::service_impl!(PUT, ChangePrimaryAddressService, ChangePrimaryAddressServicePutIn, ());
pub struct ClientClassifierResultService;
crate::service_impl!(declare, ClientClassifierResultService, "tutanota/clientclassifierresultservice", 98);
crate::service_impl!(declare, ClientClassifierResultService, "tutanota/clientclassifierresultservice", 99);
crate::service_impl!(POST, ClientClassifierResultService, ClientClassifierResultPostIn, ());
pub struct ContactListGroupService;
crate::service_impl!(declare, ContactListGroupService, "tutanota/contactlistgroupservice", 98);
crate::service_impl!(declare, ContactListGroupService, "tutanota/contactlistgroupservice", 99);
crate::service_impl!(POST, ContactListGroupService, UserAreaGroupPostData, CreateGroupPostReturn);
crate::service_impl!(DELETE, ContactListGroupService, UserAreaGroupDeleteData, ());
pub struct CustomerAccountService;
crate::service_impl!(declare, CustomerAccountService, "tutanota/customeraccountservice", 98);
crate::service_impl!(declare, CustomerAccountService, "tutanota/customeraccountservice", 99);
crate::service_impl!(POST, CustomerAccountService, CustomerAccountCreateData, ());
pub struct DraftService;
crate::service_impl!(declare, DraftService, "tutanota/draftservice", 98);
crate::service_impl!(declare, DraftService, "tutanota/draftservice", 99);
crate::service_impl!(POST, DraftService, DraftCreateData, DraftCreateReturn);
crate::service_impl!(PUT, DraftService, DraftUpdateData, DraftUpdateReturn);
pub struct EncryptTutanotaPropertiesService;
crate::service_impl!(declare, EncryptTutanotaPropertiesService, "tutanota/encrypttutanotapropertiesservice", 98);
crate::service_impl!(declare, EncryptTutanotaPropertiesService, "tutanota/encrypttutanotapropertiesservice", 99);
crate::service_impl!(POST, EncryptTutanotaPropertiesService, EncryptTutanotaPropertiesData, ());
pub struct EntropyService;
crate::service_impl!(declare, EntropyService, "tutanota/entropyservice", 98);
crate::service_impl!(declare, EntropyService, "tutanota/entropyservice", 99);
crate::service_impl!(PUT, EntropyService, EntropyData, ());
pub struct ExternalUserService;
crate::service_impl!(declare, ExternalUserService, "tutanota/externaluserservice", 98);
crate::service_impl!(declare, ExternalUserService, "tutanota/externaluserservice", 99);
crate::service_impl!(POST, ExternalUserService, ExternalUserData, ());
pub struct GroupInvitationService;
crate::service_impl!(declare, GroupInvitationService, "tutanota/groupinvitationservice", 98);
crate::service_impl!(declare, GroupInvitationService, "tutanota/groupinvitationservice", 99);
crate::service_impl!(POST, GroupInvitationService, GroupInvitationPostData, GroupInvitationPostReturn);
crate::service_impl!(PUT, GroupInvitationService, GroupInvitationPutData, ());
crate::service_impl!(DELETE, GroupInvitationService, GroupInvitationDeleteData, ());
@ -132,26 +132,26 @@ crate::service_impl!(DELETE, GroupInvitationService, GroupInvitationDeleteData,
pub struct ImportMailService;
crate::service_impl!(declare, ImportMailService, "tutanota/importmailservice", 98);
crate::service_impl!(declare, ImportMailService, "tutanota/importmailservice", 99);
crate::service_impl!(POST, ImportMailService, ImportMailPostIn, ImportMailPostOut);
crate::service_impl!(GET, ImportMailService, ImportMailGetIn, ImportMailGetOut);
pub struct ListUnsubscribeService;
crate::service_impl!(declare, ListUnsubscribeService, "tutanota/listunsubscribeservice", 98);
crate::service_impl!(declare, ListUnsubscribeService, "tutanota/listunsubscribeservice", 99);
crate::service_impl!(POST, ListUnsubscribeService, ListUnsubscribeData, ());
pub struct MailExportTokenService;
crate::service_impl!(declare, MailExportTokenService, "tutanota/mailexporttokenservice", 98);
crate::service_impl!(declare, MailExportTokenService, "tutanota/mailexporttokenservice", 99);
crate::service_impl!(POST, MailExportTokenService, (), MailExportTokenServicePostOut);
pub struct MailFolderService;
crate::service_impl!(declare, MailFolderService, "tutanota/mailfolderservice", 98);
crate::service_impl!(declare, MailFolderService, "tutanota/mailfolderservice", 99);
crate::service_impl!(POST, MailFolderService, CreateMailFolderData, CreateMailFolderReturn);
crate::service_impl!(PUT, MailFolderService, UpdateMailFolderData, ());
crate::service_impl!(DELETE, MailFolderService, DeleteMailFolderData, ());
@ -159,99 +159,99 @@ crate::service_impl!(DELETE, MailFolderService, DeleteMailFolderData, ());
pub struct MailGroupService;
crate::service_impl!(declare, MailGroupService, "tutanota/mailgroupservice", 98);
crate::service_impl!(declare, MailGroupService, "tutanota/mailgroupservice", 99);
crate::service_impl!(POST, MailGroupService, CreateMailGroupData, MailGroupPostOut);
crate::service_impl!(DELETE, MailGroupService, DeleteGroupData, ());
pub struct MailService;
crate::service_impl!(declare, MailService, "tutanota/mailservice", 98);
crate::service_impl!(declare, MailService, "tutanota/mailservice", 99);
crate::service_impl!(DELETE, MailService, DeleteMailData, ());
pub struct ManageLabelService;
crate::service_impl!(declare, ManageLabelService, "tutanota/managelabelservice", 98);
crate::service_impl!(declare, ManageLabelService, "tutanota/managelabelservice", 99);
crate::service_impl!(POST, ManageLabelService, ManageLabelServicePostIn, ());
crate::service_impl!(DELETE, ManageLabelService, ManageLabelServiceDeleteIn, ());
pub struct MoveMailService;
crate::service_impl!(declare, MoveMailService, "tutanota/movemailservice", 98);
crate::service_impl!(declare, MoveMailService, "tutanota/movemailservice", 99);
crate::service_impl!(POST, MoveMailService, MoveMailData, MoveMailPostOut);
pub struct NewsService;
crate::service_impl!(declare, NewsService, "tutanota/newsservice", 98);
crate::service_impl!(declare, NewsService, "tutanota/newsservice", 99);
crate::service_impl!(POST, NewsService, NewsIn, ());
crate::service_impl!(GET, NewsService, (), NewsOut);
pub struct PopulateClientSpamTrainingDataService;
crate::service_impl!(declare, PopulateClientSpamTrainingDataService, "tutanota/populateclientspamtrainingdataservice", 98);
crate::service_impl!(declare, PopulateClientSpamTrainingDataService, "tutanota/populateclientspamtrainingdataservice", 99);
crate::service_impl!(POST, PopulateClientSpamTrainingDataService, PopulateClientSpamTrainingDataPostIn, ());
pub struct ProcessInboxService;
crate::service_impl!(declare, ProcessInboxService, "tutanota/processinboxservice", 98);
crate::service_impl!(declare, ProcessInboxService, "tutanota/processinboxservice", 99);
crate::service_impl!(POST, ProcessInboxService, ProcessInboxPostIn, ());
pub struct ReceiveInfoService;
crate::service_impl!(declare, ReceiveInfoService, "tutanota/receiveinfoservice", 98);
crate::service_impl!(declare, ReceiveInfoService, "tutanota/receiveinfoservice", 99);
crate::service_impl!(POST, ReceiveInfoService, ReceiveInfoServiceData, ReceiveInfoServicePostOut);
pub struct ReportMailService;
crate::service_impl!(declare, ReportMailService, "tutanota/reportmailservice", 98);
crate::service_impl!(declare, ReportMailService, "tutanota/reportmailservice", 99);
crate::service_impl!(POST, ReportMailService, ReportMailPostData, ());
pub struct ResolveConversationsService;
crate::service_impl!(declare, ResolveConversationsService, "tutanota/resolveconversationsservice", 98);
crate::service_impl!(declare, ResolveConversationsService, "tutanota/resolveconversationsservice", 99);
crate::service_impl!(GET, ResolveConversationsService, ResolveConversationsServiceGetIn, ResolveConversationsServiceGetOut);
pub struct SendDraftService;
crate::service_impl!(declare, SendDraftService, "tutanota/senddraftservice", 98);
crate::service_impl!(declare, SendDraftService, "tutanota/senddraftservice", 99);
crate::service_impl!(POST, SendDraftService, SendDraftData, SendDraftReturn);
pub struct SimpleMoveMailService;
crate::service_impl!(declare, SimpleMoveMailService, "tutanota/simplemovemailservice", 98);
crate::service_impl!(declare, SimpleMoveMailService, "tutanota/simplemovemailservice", 99);
crate::service_impl!(POST, SimpleMoveMailService, SimpleMoveMailPostIn, MoveMailPostOut);
pub struct TemplateGroupService;
crate::service_impl!(declare, TemplateGroupService, "tutanota/templategroupservice", 98);
crate::service_impl!(declare, TemplateGroupService, "tutanota/templategroupservice", 99);
crate::service_impl!(POST, TemplateGroupService, UserAreaGroupPostData, CreateGroupPostReturn);
crate::service_impl!(DELETE, TemplateGroupService, UserAreaGroupDeleteData, ());
pub struct TranslationService;
crate::service_impl!(declare, TranslationService, "tutanota/translationservice", 98);
crate::service_impl!(declare, TranslationService, "tutanota/translationservice", 99);
crate::service_impl!(GET, TranslationService, TranslationGetIn, TranslationGetOut);
pub struct UnreadMailStateService;
crate::service_impl!(declare, UnreadMailStateService, "tutanota/unreadmailstateservice", 98);
crate::service_impl!(declare, UnreadMailStateService, "tutanota/unreadmailstateservice", 99);
crate::service_impl!(POST, UnreadMailStateService, UnreadMailStatePostIn, ());
pub struct UserAccountService;
crate::service_impl!(declare, UserAccountService, "tutanota/useraccountservice", 98);
crate::service_impl!(declare, UserAccountService, "tutanota/useraccountservice", 99);
crate::service_impl!(POST, UserAccountService, UserAccountCreateData, UserAccountPostOut);

View file

@ -2,7 +2,7 @@
"12": {
"name": "ReadCounterData",
"app": "monitor",
"version": 36,
"version": 37,
"since": 1,
"type": "DATA_TRANSFER_TYPE",
"id": 12,
@ -49,7 +49,7 @@
"16": {
"name": "ReadCounterReturn",
"app": "monitor",
"version": 36,
"version": 37,
"since": 1,
"type": "DATA_TRANSFER_TYPE",
"id": 16,
@ -90,7 +90,7 @@
"49": {
"name": "WriteCounterData",
"app": "monitor",
"version": 36,
"version": 37,
"since": 4,
"type": "DATA_TRANSFER_TYPE",
"id": 49,
@ -145,7 +145,7 @@
"221": {
"name": "ApprovalMail",
"app": "monitor",
"version": 36,
"version": 37,
"since": 14,
"type": "LIST_ELEMENT_TYPE",
"id": 221,
@ -226,7 +226,7 @@
"300": {
"name": "CounterValue",
"app": "monitor",
"version": 36,
"version": 37,
"since": 22,
"type": "AGGREGATED_TYPE",
"id": 300,
@ -265,7 +265,7 @@
"305": {
"name": "ErrorReportFile",
"app": "monitor",
"version": 36,
"version": 37,
"since": 23,
"type": "AGGREGATED_TYPE",
"id": 305,
@ -304,7 +304,7 @@
"316": {
"name": "ErrorReportData",
"app": "monitor",
"version": 36,
"version": 37,
"since": 23,
"type": "AGGREGATED_TYPE",
"id": 316,
@ -399,7 +399,7 @@
"335": {
"name": "ReportErrorIn",
"app": "monitor",
"version": 36,
"version": 37,
"since": 23,
"type": "DATA_TRANSFER_TYPE",
"id": 335,

File diff suppressed because it is too large Load diff