mirror of
https://github.com/tutao/tutanota.git
synced 2025-12-08 06:09:50 +00:00
allow read only cache part one, #1163
This commit is contained in:
parent
f24f5db589
commit
2d70f85273
6 changed files with 139 additions and 47 deletions
|
|
@ -8,10 +8,11 @@ import {
|
|||
getLetId,
|
||||
HttpMethod,
|
||||
isSameTypeRef,
|
||||
READ_ONLY_HEADER,
|
||||
resolveTypeReference,
|
||||
TypeRef
|
||||
} from "../../common/EntityFunctions"
|
||||
import {OperationType} from "../../common/TutanotaConstants"
|
||||
import {MailState, OperationType} from "../../common/TutanotaConstants"
|
||||
import {flat, remove} from "../../common/utils/ArrayUtils"
|
||||
import {clone, downcast, neverNull} from "../../common/utils/Utils"
|
||||
import {PermissionTypeRef} from "../../entities/sys/Permission"
|
||||
|
|
@ -23,11 +24,13 @@ import {StatisticLogEntryTypeRef} from "../../entities/tutanota/StatisticLogEntr
|
|||
import {BucketPermissionTypeRef} from "../../entities/sys/BucketPermission"
|
||||
import {SecondFactorTypeRef} from "../../entities/sys/SecondFactor"
|
||||
import {RecoverCodeTypeRef} from "../../entities/sys/RecoverCode"
|
||||
import {MailTypeRef} from "../../entities/tutanota/Mail"
|
||||
|
||||
const ValueType = EC.ValueType
|
||||
|
||||
assertWorkerOrNode()
|
||||
|
||||
|
||||
/**
|
||||
* This implementation provides a caching mechanism to the rest chain.
|
||||
* It forwards requests to the entity rest client.
|
||||
|
|
@ -94,21 +97,24 @@ export class EntityRestCache implements EntityRestInterface {
|
|||
}
|
||||
|
||||
entityRequest<T>(typeRef: TypeRef<T>, method: HttpMethodEnum, listId: ?Id, id: ?Id, entity: ?T, queryParameter: ?Params, extraHeaders?: Params): Promise<any> {
|
||||
let readOnly = false
|
||||
if (extraHeaders) {
|
||||
readOnly = extraHeaders[READ_ONLY_HEADER] === "true"
|
||||
delete extraHeaders[READ_ONLY_HEADER]
|
||||
}
|
||||
if (method === HttpMethod.GET && !this._ignoredTypes.find(ref => isSameTypeRef(typeRef, ref))) {
|
||||
if ((typeRef.app === "monitor") || (queryParameter && queryParameter["version"])) {
|
||||
// monitor app and version requests are never cached
|
||||
return this._entityRestClient.entityRequest(typeRef, method, listId, id, entity, queryParameter, extraHeaders)
|
||||
} else if (!id && queryParameter && queryParameter["ids"]) {
|
||||
return this._getMultiple(typeRef, method, listId, id, entity, queryParameter, extraHeaders)
|
||||
} else if (listId && !id && queryParameter && queryParameter["start"] !== null && queryParameter["start"]
|
||||
!== undefined && queryParameter["count"] !== null && queryParameter["count"] !== undefined
|
||||
&& queryParameter["reverse"]) { // check for null and undefined because "" and 0 are als falsy
|
||||
return this._getMultiple(typeRef, method, listId, id, entity, queryParameter, extraHeaders, readOnly)
|
||||
} else if (this.isRangeRequest(listId, id, queryParameter)) {
|
||||
// load range
|
||||
return resolveTypeReference(typeRef).then(typeModel => {
|
||||
if (typeModel.values["_id"].type === ValueType.GeneratedId) {
|
||||
let params = neverNull(queryParameter)
|
||||
return this._loadRange(downcast(typeRef), neverNull(listId), params["start"], Number(params["count"]), params["reverse"]
|
||||
=== "true")
|
||||
=== "true", readOnly)
|
||||
} else {
|
||||
// we currently only store ranges for generated ids
|
||||
return this._entityRestClient.entityRequest(typeRef, method, listId, id, entity, queryParameter, extraHeaders)
|
||||
|
|
@ -121,7 +127,9 @@ export class EntityRestCache implements EntityRestInterface {
|
|||
} else {
|
||||
return this._entityRestClient.entityRequest(typeRef, method, listId, id, entity, queryParameter, extraHeaders)
|
||||
.then(entity => {
|
||||
this._putIntoCache(entity)
|
||||
if (!readOnly) {
|
||||
this._putIntoCache(entity)
|
||||
}
|
||||
return entity
|
||||
})
|
||||
}
|
||||
|
|
@ -134,8 +142,15 @@ export class EntityRestCache implements EntityRestInterface {
|
|||
}
|
||||
}
|
||||
|
||||
isRangeRequest(listId: ?Id, id: ?Id, queryParameter: ?Params) {
|
||||
// check for null and undefined because "" and 0 are als falsy
|
||||
return listId && !id
|
||||
&& queryParameter && queryParameter["start"] !== null && queryParameter["start"] !== undefined && queryParameter["count"] !== null
|
||||
&& queryParameter["count"] !== undefined && queryParameter["reverse"]
|
||||
}
|
||||
|
||||
_getMultiple<T>(typeRef: TypeRef<T>, method: HttpMethodEnum, listId: ?Id, id: ?Id, entity: ?T, queryParameter: Params,
|
||||
extraHeaders?: Params): Promise<Array<T>> {
|
||||
extraHeaders?: Params, readOnly: boolean): Promise<Array<T>> {
|
||||
const ids = queryParameter["ids"].split(",")
|
||||
const inCache = [], notInCache = []
|
||||
ids.forEach((id) => {
|
||||
|
|
@ -150,17 +165,18 @@ export class EntityRestCache implements EntityRestInterface {
|
|||
this._entityRestClient.entityRequest(typeRef, method, listId, id, entity, newQuery, extraHeaders)
|
||||
.then((response) => {
|
||||
const entities: Array<T> = downcast(response)
|
||||
entities.forEach((e) => this._putIntoCache(e))
|
||||
if (!readOnly) {
|
||||
entities.forEach((e) => this._putIntoCache(e))
|
||||
}
|
||||
return entities
|
||||
}),
|
||||
inCache.map(id => this._getFromCache(typeRef, listId, id))
|
||||
]).then(flat)
|
||||
}
|
||||
|
||||
_loadRange<T: ListElement>(typeRef: TypeRef<T>, listId: Id, start: Id, count: number, reverse: boolean): Promise<T[]> {
|
||||
_loadRange<T: ListElement>(typeRef: TypeRef<T>, listId: Id, start: Id, count: number, reverse: boolean, readOnly: boolean): Promise<T[]> {
|
||||
let path = typeRefToPath(typeRef)
|
||||
let listCache = (this._listEntities[path]
|
||||
&& this._listEntities[path][listId]) ? this._listEntities[path][listId] : null
|
||||
const listCache = (this._listEntities[path] && this._listEntities[path][listId]) ? this._listEntities[path][listId] : null
|
||||
// check which range must be loaded from server
|
||||
if (!listCache || (start === GENERATED_MAX_ID && reverse && listCache.upperRangeId !== GENERATED_MAX_ID)
|
||||
|| (start === GENERATED_MIN_ID && !reverse && listCache.lowerRangeId !== GENERATED_MIN_ID)) {
|
||||
|
|
@ -173,23 +189,30 @@ export class EntityRestCache implements EntityRestInterface {
|
|||
count: String(count),
|
||||
reverse: String(reverse)
|
||||
}).then(result => {
|
||||
let entities = ((result: any): T[])
|
||||
let entities: Array<T> = downcast(result)
|
||||
// create the list data path in the cache if not existing
|
||||
if (readOnly) {
|
||||
return entities;
|
||||
}
|
||||
|
||||
let newListCache
|
||||
if (!listCache) {
|
||||
if (!this._listEntities[path]) {
|
||||
this._listEntities[path] = {}
|
||||
}
|
||||
listCache = {allRange: [], lowerRangeId: start, upperRangeId: start, elements: {}}
|
||||
this._listEntities[path][listId] = listCache
|
||||
newListCache = {allRange: [], lowerRangeId: start, upperRangeId: start, elements: {}}
|
||||
this._listEntities[path][listId] = newListCache
|
||||
} else {
|
||||
listCache.allRange = []
|
||||
listCache.lowerRangeId = start
|
||||
listCache.upperRangeId = start
|
||||
newListCache = listCache
|
||||
newListCache.allRange = []
|
||||
newListCache.lowerRangeId = start
|
||||
newListCache.upperRangeId = start
|
||||
}
|
||||
return this._handleElementRangeResult(listCache, start, count, reverse, entities, count)
|
||||
return this._handleElementRangeResult(newListCache, start, count, reverse, entities, count)
|
||||
})
|
||||
} else if (!firstBiggerThanSecond(start, listCache.upperRangeId)
|
||||
&& !firstBiggerThanSecond(listCache.lowerRangeId, start)) { // check if the requested start element is located in the range
|
||||
|
||||
// count the numbers of elements that are already in allRange to determine the number of elements to read
|
||||
let newRequestParams = this._getNumberOfElementsToRead(listCache, start, count, reverse)
|
||||
if (newRequestParams.newCount > 0) {
|
||||
|
|
@ -197,15 +220,33 @@ export class EntityRestCache implements EntityRestInterface {
|
|||
start: newRequestParams.newStart,
|
||||
count: String(newRequestParams.newCount),
|
||||
reverse: String(reverse)
|
||||
}).then(entities => {
|
||||
return this._handleElementRangeResult(neverNull(listCache), start, count, reverse, ((entities: any): T[]), newRequestParams.newCount)
|
||||
}).then(result => {
|
||||
let entities: Array<T> = downcast(result)
|
||||
if (readOnly) {
|
||||
const cachedEntities = this._provideFromCache(listCache, start, count - newRequestParams.newCount, reverse)
|
||||
if (reverse) {
|
||||
return entities.concat(cachedEntities)
|
||||
} else {
|
||||
return cachedEntities.concat(entities)
|
||||
}
|
||||
} else {
|
||||
return this._handleElementRangeResult(neverNull(listCache), start, count, reverse, entities, newRequestParams.newCount)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// all elements are located in the cache.
|
||||
return Promise.resolve(this._provideFromCache(listCache, start, count, reverse))
|
||||
}
|
||||
} else if ((firstBiggerThanSecond(start, listCache.upperRangeId) && !reverse)
|
||||
} else if ((firstBiggerThanSecond(start, listCache.upperRangeId) && !reverse) // Start is outside the range.
|
||||
|| (firstBiggerThanSecond(listCache.lowerRangeId, start) && reverse)) {
|
||||
if (readOnly) {
|
||||
// Doesn't make any sense to read from existing range because we know that elements are not in the cache
|
||||
return this.entityRequest(typeRef, HttpMethod.GET, listId, null, null, {
|
||||
start: start,
|
||||
count: String(count),
|
||||
reverse: String(reverse)
|
||||
})
|
||||
}
|
||||
let loadStartId
|
||||
if (firstBiggerThanSecond(start, listCache.upperRangeId) && !reverse) {
|
||||
// start is higher than range. load from upper range id with same count. then, if all available elements have been loaded or the requested number is in cache, return from cache. otherwise load again the same way.
|
||||
|
|
@ -368,6 +409,13 @@ export class EntityRestCache implements EntityRestInterface {
|
|||
let typeRef = new TypeRef(data.application, data.type)
|
||||
if (data.operation === OperationType.UPDATE) {
|
||||
if (this._isInCache(typeRef, data.instanceListId, data.instanceId)) {
|
||||
if (isSameTypeRef(MailTypeRef, typeRef)) {
|
||||
// ignore update of owner enc session key to avoid loading mail instance twice.
|
||||
const cachedMail: Mail = downcast(this._getFromCache(typeRef, data.instanceListId, data.instanceId))
|
||||
if (cachedMail.state !== MailState.DRAFT) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
return this._entityRestClient.entityRequest(typeRef, HttpMethod.GET, data.instanceListId, data.instanceId)
|
||||
.then(entity => {
|
||||
this._putIntoCache(entity)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue