(core) notify home db of shares when copying/forking/uploading docs

Summary:
The first time a worker opens a document, it will now check if it has any shares the home db needs to be aware of. If so, they will be added. This is important for documents uploaded/copied/forked/replaced, so that their shares work out of the box.

In future, may want some UI to give user control of whether shares are activated after upload/copy/fork/replace.

It seems tricky currently to know if a document is being opened for the first time. As a proxy, I check whether usage information has been calculated and saved to the db, since I can determine that without adding another db query. It is safe to synchronize shares more than necessary.

This leaves two gaps:
 * If a document is created/uploaded/copied/forked/replaced and no attempt is made to access it prior to using a share, then that share won't actually be available. Not a problem currently I think, since how would a user have determined the share key. But in future it would be good to also do a sync after creation/upload/copy/fork/replacement/...
 * On document replacement, usage info is reset but not absolutely immediately. So in principle shares could fail to be created on first load of the replacement. Usage info reset could be tweaked to give a guarantee here, but also fixing the first point would resolve this second point too.

Test Plan: copy test added

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D4165
This commit is contained in:
Paul Fitzpatrick 2024-01-12 17:05:13 -05:00
parent e91dc768b8
commit 007c4492dc
2 changed files with 36 additions and 4 deletions

View File

@ -123,6 +123,7 @@ import {
DocSession,
getDocSessionAccess,
getDocSessionAltSessionId,
getDocSessionUsage,
getDocSessionUser,
getDocSessionUserId,
makeExceptionalDocSession,
@ -1829,7 +1830,15 @@ export class ActiveDoc extends EventEmitter {
));
}
public async syncShares(docSession: OptDocSession) {
/**
* Make sure the shares listed for the doc in the home db and the
* shares listed within the doc itself are in sync. If skipIfNoShares
* is set, we skip checking the home db if there are no shares listed
* within the doc, as a small optimization.
*/
public async syncShares(docSession: OptDocSession, options: {
skipIfNoShares?: boolean,
} = {}) {
const metaTables = await this.fetchMetaTables(docSession);
const shares = metaTables['_grist_Shares'];
const ids = shares[2];
@ -1841,7 +1850,9 @@ export class ActiveDoc extends EventEmitter {
options: String(vals['options'][idx]),
};
});
await this._getHomeDbManagerOrFail().syncShares(this.docName, goodShares);
if (goodShares.length > 0 || !options.skipIfNoShares) {
await this._getHomeDbManagerOrFail().syncShares(this.docName, goodShares);
}
return goodShares;
}
@ -2370,6 +2381,13 @@ export class ActiveDoc extends EventEmitter {
const closeTimeout = Math.max(loadMs, 1000) * Deps.ACTIVEDOC_TIMEOUT;
this._inactivityTimer.setDelay(closeTimeout);
this._log.debug(docSession, `loaded in ${loadMs} ms, InactivityTimer set to ${closeTimeout} ms`);
const docUsage = getDocSessionUsage(docSession);
if (!docUsage) {
// This looks be the first time this installation of Grist is touching
// the document. If it has any shares, the home db needs to know.
// TODO: could offer a UI to control whether shares are activated.
await this.syncShares(docSession, { skipIfNoShares: true });
}
void this._initializeDocUsage(docSession);
// Start the periodic work, unless this doc has already started shutting down.

View File

@ -1,4 +1,5 @@
import {BrowserSettings} from 'app/common/BrowserSettings';
import {DocumentUsage} from 'app/common/DocUsage';
import {Role} from 'app/common/roles';
import {FullUser} from 'app/common/UserAPI';
import {Document} from 'app/gen-server/entity/Document';
@ -167,11 +168,24 @@ export function getDocSessionAccess(docSession: OptDocSession): Role {
}
export function getDocSessionShare(docSession: OptDocSession): string|null {
return _getCachedDoc(docSession)?.linkId || null;
}
/**
* Get document usage seen in db when we were last checking document
* access. Not necessarily a live value when using a websocket
* (although we do recheck access periodically).
*/
export function getDocSessionUsage(docSession: OptDocSession): DocumentUsage|null {
return _getCachedDoc(docSession)?.usage || null;
}
export function _getCachedDoc(docSession: OptDocSession): Document|null {
if (docSession.authorizer) {
return docSession.authorizer.getCachedAuth().cachedDoc?.linkId || null;
return docSession.authorizer.getCachedAuth().cachedDoc || null;
}
if (docSession.req) {
return docSession.req.docAuth?.cachedDoc?.linkId || null;
return docSession.req.docAuth?.cachedDoc || null;
}
return null;
}