Split email cache into full content and stubs.
This also introduces status for in-flight requests to avoid perpetual, unnecessary loops of change-the-re-get-data.
This commit is contained in:
parent
fb53a7506f
commit
4e1922c5fa
8 changed files with 219 additions and 47 deletions
|
|
@ -6,7 +6,7 @@ import * as base64 from "base-64";
|
|||
import * as jmapclient from "jmap-client-ts";
|
||||
import { FetchTransport } from "jmap-client-ts/lib/utils/fetch-transport";
|
||||
|
||||
import { IAccount, IEmail, IMailbox, ISession } from "./types";
|
||||
import { IAccount, IEmail, IEmailStub, IMailbox, ISession } from "./types";
|
||||
|
||||
type Callback = () => void;
|
||||
|
||||
|
|
@ -16,45 +16,31 @@ export interface IAuth {
|
|||
}
|
||||
|
||||
export interface ClientState {
|
||||
inFlight: {
|
||||
emailContents: Set<string>;
|
||||
emailStubs: Set<string>;
|
||||
mailboxes: Set<string>;
|
||||
};
|
||||
session: ISession | null;
|
||||
}
|
||||
|
||||
export default class Client {
|
||||
// Get the currently active account
|
||||
account(accountId: string): IAccount | null {
|
||||
if (!(this.state.session && this.state.session.accounts)) return null;
|
||||
return this.state.session.accounts[accountId];
|
||||
}
|
||||
// All objects which currently are listening for changes
|
||||
callbacks: Array<Callback> = [];
|
||||
jclient: jmapclient.Client | null = null;
|
||||
state: ClientState = {
|
||||
inFlight: {
|
||||
emailContents: new Set(),
|
||||
emailStubs: new Set(),
|
||||
mailboxes: new Set(),
|
||||
},
|
||||
session: null,
|
||||
};
|
||||
|
||||
onChange(f: Callback) {
|
||||
this.callbacks.push(f);
|
||||
}
|
||||
|
||||
// Ensure we have the fully-fleshed email
|
||||
ensureEmailGet(accountId: string, emailId: string) {
|
||||
if (this.state.session == null) return;
|
||||
const existing = this.state.session.emails[emailId];
|
||||
if (existing != null) return;
|
||||
this.emailGet(accountId, emailId);
|
||||
}
|
||||
|
||||
// Ensure we have the list of emails for the provided account and mailbox
|
||||
ensureEmailList(accountId: string, mailboxId: string) {
|
||||
const mailbox = this.mailbox(accountId, mailboxId);
|
||||
if (mailbox != null && mailbox.emailIds != null) return;
|
||||
this.emailList(accountId, mailboxId, []);
|
||||
}
|
||||
|
||||
// Ensure that login happens. If this is called many times, only login once.
|
||||
ensureLogin(auth: IAuth) {
|
||||
if (this.jclient != null) return;
|
||||
this.doLogin(auth);
|
||||
// Get the currently active account
|
||||
account(accountId: string): IAccount | null {
|
||||
if (!(this.state.session && this.state.session.accounts)) return null;
|
||||
return this.state.session.accounts[accountId];
|
||||
}
|
||||
|
||||
// Make the request to get system metadata
|
||||
|
|
@ -81,36 +67,110 @@ export default class Client {
|
|||
return;
|
||||
}
|
||||
|
||||
// Ensure we have the full email content
|
||||
ensureEmailContent(accountId: string, emailId: string) {
|
||||
if (this.state.session == null) return;
|
||||
const existing = this.state.session.emails[emailId];
|
||||
if (existing != null) return;
|
||||
this.emailGetContent(accountId, emailId);
|
||||
}
|
||||
|
||||
// Ensure we have the email summary
|
||||
ensureEmailStub(accountId: string, emailId: string) {
|
||||
if (this.state.session == null) return;
|
||||
const existing = this.state.session.emailStubs[emailId];
|
||||
if (existing != null) return;
|
||||
this.emailGetStub(accountId, emailId);
|
||||
}
|
||||
|
||||
// Ensure we have the list of emails for the provided account and mailbox
|
||||
ensureEmailList(accountId: string, mailboxId: string) {
|
||||
const mailbox = this.mailbox(accountId, mailboxId);
|
||||
if (mailbox != null && mailbox.emailIds != null) return;
|
||||
this.emailList(accountId, mailboxId, []);
|
||||
}
|
||||
|
||||
// Ensure that login happens. If this is called many times, only login once.
|
||||
ensureLogin(auth: IAuth) {
|
||||
if (this.jclient != null) return;
|
||||
this.doLogin(auth);
|
||||
}
|
||||
|
||||
email(emailId: string): IEmail | null {
|
||||
if (this.state.session == null) return null;
|
||||
return this.state.session.emails[emailId];
|
||||
}
|
||||
|
||||
emailGet(accountId: string, emailId: string) {
|
||||
emailGetContent(accountId: string, emailId: string) {
|
||||
if (this.jclient == null) return;
|
||||
|
||||
if (this.state.session == null) return;
|
||||
if (this.state.inFlight.emailContents.has(emailId)) return;
|
||||
this.state.inFlight.emailContents.add(emailId);
|
||||
const msg = "Email get content";
|
||||
this.jclient
|
||||
.email_get({
|
||||
accountId: accountId,
|
||||
ids: [emailId],
|
||||
properties: ["mailboxIds", "subject"],
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("Email response", response);
|
||||
console.log(msg, "response", response);
|
||||
response.list.forEach((e) => {
|
||||
if (this.state.session == null) return;
|
||||
const existing = this.state.session.emails[e.id];
|
||||
const existing = this.state.session!.emails[e.id];
|
||||
if (existing !== undefined && existing.subject !== e.subject) {
|
||||
console.error(
|
||||
"Expectations violation: email ID is not unique within the server!",
|
||||
);
|
||||
}
|
||||
this.state.session.emails[e.id] = e;
|
||||
this._triggerChange("Email " + e.id);
|
||||
this.state.session!.emails[e.id] = {
|
||||
...e,
|
||||
id: e.id,
|
||||
};
|
||||
this._triggerChange(msg + e.id);
|
||||
});
|
||||
})
|
||||
.catch((x) => {
|
||||
console.error("Failed to get email", emailId, x);
|
||||
console.error("Failed to get email content", emailId, x);
|
||||
})
|
||||
.finally(() => {
|
||||
this.state.inFlight.emailContents.delete(emailId);
|
||||
});
|
||||
}
|
||||
|
||||
emailGetStub(accountId: string, emailId: string) {
|
||||
if (this.jclient == null) return;
|
||||
if (this.state.session == null) return;
|
||||
if (this.state.inFlight.emailStubs.has(emailId)) return;
|
||||
this.state.inFlight.emailStubs.add(emailId);
|
||||
const msg = "Email get summary";
|
||||
this.jclient
|
||||
.email_get({
|
||||
accountId: accountId,
|
||||
ids: [emailId],
|
||||
properties: ["subject"],
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(msg, "response", response);
|
||||
response.list.forEach((e) => {
|
||||
if (this.state.session == null) return;
|
||||
const existing = this.state.session.emailStubs[e.id];
|
||||
if (existing !== undefined && existing.subject !== e.subject) {
|
||||
console.error(
|
||||
"Expectations violation: email ID is not unique within the server!",
|
||||
);
|
||||
}
|
||||
this.state.session.emailStubs[e.id] = {
|
||||
id: e.id,
|
||||
subject: e.subject,
|
||||
};
|
||||
this._triggerChange(msg + e.id);
|
||||
});
|
||||
})
|
||||
.catch((x) => {
|
||||
console.error("Failed to get email stub", emailId, x);
|
||||
})
|
||||
.finally(() => {
|
||||
this.state.inFlight.emailStubs.delete(emailId);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +192,11 @@ export default class Client {
|
|||
});
|
||||
}
|
||||
|
||||
emailStub(emailId: string): IEmailStub | null {
|
||||
if (this.state.session == null) return null;
|
||||
return this.state.session.emailStubs[emailId];
|
||||
}
|
||||
|
||||
mailbox(accountId: string, mailboxId: string): IMailbox | null {
|
||||
if (this.state.session == null) return null;
|
||||
const account = this.state.session.accounts[accountId];
|
||||
|
|
@ -161,7 +226,6 @@ export default class Client {
|
|||
mailboxes.push({
|
||||
...m,
|
||||
emailIds: null,
|
||||
emails: null,
|
||||
});
|
||||
});
|
||||
account.mailboxes = mailboxes;
|
||||
|
|
@ -169,6 +233,10 @@ export default class Client {
|
|||
});
|
||||
}
|
||||
|
||||
onChange(f: Callback) {
|
||||
this.callbacks.push(f);
|
||||
}
|
||||
|
||||
_triggerChange(msg: string) {
|
||||
console.log("Client change", msg);
|
||||
this.callbacks.forEach((c) => {
|
||||
|
|
@ -191,6 +259,7 @@ export default class Client {
|
|||
]),
|
||||
),
|
||||
emails: {},
|
||||
emailStubs: {},
|
||||
};
|
||||
if (!this.state.session) return;
|
||||
this._triggerChange("Session");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue