Skip to content

Commit

Permalink
skip spam email during deriving unread value from local store (2)
Browse files Browse the repository at this point in the history
* closes #86
  • Loading branch information
vladimiry committed Dec 28, 2018
1 parent ab79d19 commit 14e6db1
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 106 deletions.
181 changes: 77 additions & 104 deletions src/electron-main/api/endpoints-builders/database/folders-view.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import R from "ramda";

import {
CONVERSATION_TYPE,
ConversationEntry,
Folder,
FsDb,
FsDbAccount,
MAIL_FOLDER_TYPE,
PROTONMAIL_MAILBOX_IDENTIFIERS,
View,
} from "src/shared/model/database";
import {CONVERSATION_TYPE, ConversationEntry, FsDb, FsDbAccount, MAIL_FOLDER_TYPE, View} from "src/shared/model/database";
import {resolveFsAccountFolders} from "src/electron-main/database/util";
import {walkConversationNodesTree} from "src/shared/util";

const splitAndFormatFolders: (folders: View.Folder[]) => {
Expand Down Expand Up @@ -78,109 +70,90 @@ const splitAndFormatFolders: (folders: View.Folder[]) => {
},
});

const buildRootNodes = ((staticProtonmailFolders: Folder[]) => {
return <T extends keyof FsDb["accounts"]>(
account: FsDbAccount<T>,
): { rootNodes: View.ConversationNode[]; folders: View.Folder[]; } => {
const conversationEntries: ConversationEntry[] = account.metadata.type === "tutanota"
? Object.values(account.conversationEntries)
: ((
buildEntry = ({pk, mailPk}: Pick<ConversationEntry, "pk" | "mailPk">) => ({
pk,
id: pk,
raw: "{}",
messageId: "",
// TODO consider filling "conversationType" based on "mail.replyType"
conversationType: CONVERSATION_TYPE.UNEXPECTED,
mailPk,
}),
entriesMappedByPk = new Map<ConversationEntry["pk"], ConversationEntry>(),
) => {
for (const mail of Object.values(account.mails)) {
const rootEntryPk = mail.conversationEntryPk;
const mailEntryPk = `${rootEntryPk}:${mail.pk}`;
entriesMappedByPk.set(rootEntryPk, entriesMappedByPk.get(rootEntryPk) || buildEntry({pk: rootEntryPk}));
entriesMappedByPk.set(mailEntryPk, {
...buildEntry({pk: mailEntryPk, mailPk: mail.pk}),
previousPk: rootEntryPk,
});
}
return [...entriesMappedByPk.values()];
})();
const nodeLookup = ((nodeLookupMap = new Map<ConversationEntry["pk"], View.ConversationNode>()) => (
pk: ConversationEntry["pk"] | Required<ConversationEntry>["previousPk"],
node: View.ConversationNode = {entryPk: pk, children: []},
): View.ConversationNode => {
node = nodeLookupMap.get(pk) || node;
if (!nodeLookupMap.has(pk)) {
nodeLookupMap.set(pk, node);
function buildRootNodes<T extends keyof FsDb["accounts"]>(
account: FsDbAccount<T>,
): { rootNodes: View.ConversationNode[]; folders: View.Folder[]; } {
const conversationEntries: ConversationEntry[] = account.metadata.type === "tutanota"
? Object.values(account.conversationEntries)
// building virtual conversation entries for protonmail
: ((
buildEntry = ({pk, mailPk}: Pick<ConversationEntry, "pk" | "mailPk">) => ({
pk,
id: pk,
raw: "{}",
messageId: "",
// TODO consider filling "conversationType" based on "mail.replyType"
conversationType: CONVERSATION_TYPE.UNEXPECTED,
mailPk,
}),
entriesMappedByPk = new Map<ConversationEntry["pk"], ConversationEntry>(),
) => {
for (const mail of Object.values(account.mails)) {
const rootEntryPk = mail.conversationEntryPk;
const mailEntryPk = `${rootEntryPk}:${mail.pk}`;
entriesMappedByPk.set(rootEntryPk, entriesMappedByPk.get(rootEntryPk) || buildEntry({pk: rootEntryPk}));
entriesMappedByPk.set(mailEntryPk, {
...buildEntry({pk: mailEntryPk, mailPk: mail.pk}),
previousPk: rootEntryPk,
});
}
return node;
return [...entriesMappedByPk.values()];
})();
const folders: View.Folder[] = Array.from(
account.metadata.type === "tutanota"
? Object.values(account.folders)
: staticProtonmailFolders.concat(Object.values(account.folders)),
(folder) => ({...folder, rootConversationNodes: []}),
);
const rootNodes: View.ConversationNode[] = [];
const resolveFolder = ((map = new Map(folders.reduce(
(entries: Array<[View.Folder["mailFolderId"], View.Folder]>, folder) => entries.concat([[folder.mailFolderId, folder]]),
[],
))) => ({mailFolderId}: Pick<View.Folder, "mailFolderId">) => map.get(mailFolderId))();

for (const entry of conversationEntries) {
const node = nodeLookup(entry.pk);
const resolvedMail = entry.mailPk && account.mails[entry.mailPk];

node.mail = resolvedMail
? {
// TODO use "pick" instead of "omit", ie prefer whitelisting over blacklisting
...R.omit(["raw", "body", "attachments"], resolvedMail),
folders: [],
}
: undefined;

if (node.mail) {
for (const mailFolderId of node.mail.mailFolderIds) {
const folder = resolveFolder({mailFolderId});
if (!folder) {
continue;
}
node.mail.folders.push(folder);
}
const nodeLookup = ((nodeLookupMap = new Map<ConversationEntry["pk"], View.ConversationNode>()) => (
pk: ConversationEntry["pk"] | Required<ConversationEntry>["previousPk"],
node: View.ConversationNode = {entryPk: pk, children: []},
): View.ConversationNode => {
node = nodeLookupMap.get(pk) || node;
if (!nodeLookupMap.has(pk)) {
nodeLookupMap.set(pk, node);
}
return node;
})();
const folders: View.Folder[] = Array.from(
resolveFsAccountFolders(account),
(folder) => ({...folder, rootConversationNodes: []}),
);
const resolveFolder = ((map = new Map(folders.reduce(
(entries: Array<[View.Folder["mailFolderId"], View.Folder]>, folder) => entries.concat([[folder.mailFolderId, folder]]),
[],
))) => ({mailFolderId}: Pick<View.Folder, "mailFolderId">) => map.get(mailFolderId))();
const rootNodes: View.ConversationNode[] = [];

for (const entry of conversationEntries) {
const node = nodeLookup(entry.pk);
const resolvedMail = entry.mailPk && account.mails[entry.mailPk];

node.mail = resolvedMail
? {
// TODO use "pick" instead of "omit", ie prefer whitelisting over blacklisting
...R.omit(["raw", "body", "attachments"], resolvedMail),
folders: [],
}
: undefined;

if (!entry.previousPk) {
rootNodes.push(node);
continue;
if (node.mail) {
for (const mailFolderId of node.mail.mailFolderIds) {
const folder = resolveFolder({mailFolderId});
if (!folder) {
continue;
}
node.mail.folders.push(folder);
}
}

nodeLookup(entry.previousPk).children.push(node);
if (!entry.previousPk) {
rootNodes.push(node);
continue;
}

return {
rootNodes,
folders,
};
nodeLookup(entry.previousPk).children.push(node);
}

return {
rootNodes,
folders,
};
})(([
[PROTONMAIL_MAILBOX_IDENTIFIERS.Inbox, MAIL_FOLDER_TYPE.INBOX],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Drafts, MAIL_FOLDER_TYPE.DRAFT],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Sent, MAIL_FOLDER_TYPE.SENT],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Starred, MAIL_FOLDER_TYPE.STARRED],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Archive, MAIL_FOLDER_TYPE.ARCHIVE],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Spam, MAIL_FOLDER_TYPE.SPAM],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Trash, MAIL_FOLDER_TYPE.TRASH],
[PROTONMAIL_MAILBOX_IDENTIFIERS["All Mail"], MAIL_FOLDER_TYPE.ALL],
] as Array<[Folder["id"], Folder["folderType"]]>).map(([id, folderType]) => ({
pk: id,
id,
raw: "{}",
folderType,
name: PROTONMAIL_MAILBOX_IDENTIFIERS._.resolveNameByValue(id as any),
mailFolderId: id,
})));
}

function fillRootNodesSummary(rootNodes: View.ConversationNode[]) {
for (const rawRootNode of rootNodes) {
Expand Down
20 changes: 20 additions & 0 deletions src/electron-main/database/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
import {Folder, MAIL_FOLDER_TYPE, PROTONMAIL_MAILBOX_IDENTIFIERS} from "src/shared/model/database";

export const DATABASE_VERSION = "2";

export const PROTONMAIL_STATIC_FOLDERS = ([
[PROTONMAIL_MAILBOX_IDENTIFIERS.Inbox, MAIL_FOLDER_TYPE.INBOX],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Drafts, MAIL_FOLDER_TYPE.DRAFT],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Sent, MAIL_FOLDER_TYPE.SENT],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Starred, MAIL_FOLDER_TYPE.STARRED],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Archive, MAIL_FOLDER_TYPE.ARCHIVE],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Spam, MAIL_FOLDER_TYPE.SPAM],
[PROTONMAIL_MAILBOX_IDENTIFIERS.Trash, MAIL_FOLDER_TYPE.TRASH],
[PROTONMAIL_MAILBOX_IDENTIFIERS["All Mail"], MAIL_FOLDER_TYPE.ALL],
] as Array<[Folder["id"], Folder["folderType"]]>).map(([id, folderType]) => ({
pk: id,
id,
raw: "{}",
folderType,
name: PROTONMAIL_MAILBOX_IDENTIFIERS._.resolveNameByValue(id as any),
mailFolderId: id,
}));
5 changes: 3 additions & 2 deletions src/electron-main/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {DATABASE_VERSION} from "./constants";
import {DbAccountPk, FsDb, FsDbAccount, MAIL_FOLDER_TYPE, Mail, MemoryDb, MemoryDbAccount} from "src/shared/model/database";
import {EntityMap} from "./entity-map";
import {curryFunctionMembers} from "src/shared/util";
import {resolveMemoryAccountFolders} from "./util";

const logger = curryFunctionMembers(_logger, "[electron-main/database]");

// TODO consider dropping Map-based databse use ("MemoryDb"), ie use ony pupe JSON-based "FsDb"
// TODO consider dropping Map-based database use ("MemoryDb"), ie use ony pupe JSON-based "FsDb"
export class Database {

private memoryDb: MemoryDb = this.buildEmptyDatabase();
Expand Down Expand Up @@ -185,7 +186,7 @@ export class Database {
accountStat(
account: MemoryDbAccount,
): { conversationEntries: number, mails: number, folders: number; contacts: number; unread: number } {
const spamFolder = [...account.folders.values()].find(({folderType}) => folderType === MAIL_FOLDER_TYPE.SPAM);
const spamFolder = resolveMemoryAccountFolders(account).find(({folderType}) => folderType === MAIL_FOLDER_TYPE.SPAM);
const spamFolderMailFolderId = spamFolder && spamFolder.mailFolderId;
const isSpamEmail: (mail: Mail) => boolean = typeof spamFolderMailFolderId !== "undefined"
? ({mailFolderIds}) => mailFolderIds.includes(spamFolderMailFolderId)
Expand Down
20 changes: 20 additions & 0 deletions src/electron-main/database/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {DbAccountPk, FsDb, FsDbAccount, MemoryDb, MemoryDbAccount} from "src/shared/model/database";
import {PROTONMAIL_STATIC_FOLDERS} from "./constants";

export function resolveFsAccountFolders<T extends keyof FsDb["accounts"]>(account: FsDbAccount<T>) {
return Object.values(account.folders).concat(
resolveAccountVirtualFolders(account.metadata.type),
);
}

export function resolveMemoryAccountFolders<T extends keyof MemoryDb["accounts"]>(account: MemoryDbAccount<T>) {
return [...account.folders.values()].concat(
resolveAccountVirtualFolders(account.metadata.type),
);
}

function resolveAccountVirtualFolders<T extends DbAccountPk["type"]>(type: T) {
return type === "protonmail"
? PROTONMAIL_STATIC_FOLDERS
: [];
}

0 comments on commit 14e6db1

Please sign in to comment.