allow read only cache part one, #1163

This commit is contained in:
ivk 2019-03-20 12:17:50 +01:00
parent f24f5db589
commit 2d70f85273
6 changed files with 139 additions and 47 deletions

View file

@ -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)