@adobe/helix-google-support 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/package.json +5 -4
- package/src/GoogleClient.d.ts +140 -0
- package/src/GoogleClient.js +273 -71
- package/src/cache.js +0 -78
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# [1.2.0](https://github.com/adobe/helix-google-support/compare/v1.1.0...v1.2.0) (2022-04-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add get file|doc from path ([#9](https://github.com/adobe/helix-google-support/issues/9)) ([4a2e927](https://github.com/adobe/helix-google-support/commit/4a2e9272f4328e936e65b7144d04e516ac602625))
|
|
7
|
+
|
|
8
|
+
# [1.1.0](https://github.com/adobe/helix-google-support/compare/v1.0.1...v1.1.0) (2022-04-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add cache invalidate for drive items ([#8](https://github.com/adobe/helix-google-support/issues/8)) ([7aa2c80](https://github.com/adobe/helix-google-support/commit/7aa2c8061facb2d3c45929356c23370d83686f55))
|
|
14
|
+
|
|
15
|
+
## [1.0.1](https://github.com/adobe/helix-google-support/compare/v1.0.0...v1.0.1) (2022-04-20)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* minor update ([#6](https://github.com/adobe/helix-google-support/issues/6)) ([8d7cd50](https://github.com/adobe/helix-google-support/commit/8d7cd50c8162da12c4d4430833416fac0f2eda93))
|
|
21
|
+
|
|
1
22
|
# 1.0.0 (2022-04-13)
|
|
2
23
|
|
|
3
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-google-support",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Helix Google Support",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -40,16 +40,17 @@
|
|
|
40
40
|
"@semantic-release/changelog": "6.0.1",
|
|
41
41
|
"@semantic-release/git": "10.0.1",
|
|
42
42
|
"@semantic-release/npm": "9.0.1",
|
|
43
|
-
"c8": "7.11.
|
|
43
|
+
"c8": "7.11.2",
|
|
44
44
|
"codecov": "3.8.3",
|
|
45
|
-
"eslint": "8.
|
|
45
|
+
"eslint": "8.14.0",
|
|
46
46
|
"eslint-plugin-header": "3.1.1",
|
|
47
47
|
"eslint-plugin-import": "2.26.0",
|
|
48
48
|
"husky": "7.0.4",
|
|
49
49
|
"junit-report-builder": "3.0.0",
|
|
50
|
-
"lint-staged": "12.
|
|
50
|
+
"lint-staged": "12.4.0",
|
|
51
51
|
"mocha": "9.2.2",
|
|
52
52
|
"mocha-multi-reporters": "1.5.1",
|
|
53
|
+
"nock": "13.2.4",
|
|
53
54
|
"semantic-release": "19.0.2"
|
|
54
55
|
},
|
|
55
56
|
"lint-staged": {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
declare interface ICachePlugin {}
|
|
13
|
+
|
|
14
|
+
declare interface DriveItemInfo {}
|
|
15
|
+
|
|
16
|
+
declare interface GoogleClientOptions {
|
|
17
|
+
log:Console;
|
|
18
|
+
clientId:string;
|
|
19
|
+
clientSecret:string;
|
|
20
|
+
redirectUri:string;
|
|
21
|
+
cachePlugin?:ICachePlugin;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Google client
|
|
26
|
+
*/
|
|
27
|
+
declare class GoogleClient {
|
|
28
|
+
/**
|
|
29
|
+
* Sets the global item cache options. It internally creates a new LRU
|
|
30
|
+
* with the given options
|
|
31
|
+
*
|
|
32
|
+
* @param opts
|
|
33
|
+
*/
|
|
34
|
+
static setItemCacheOptions(opts:object);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns a url for a google drive id.
|
|
38
|
+
* @param {string} id
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
static id2Url(id:string):string;
|
|
42
|
+
|
|
43
|
+
constructor(opts:GoogleClientOptions);
|
|
44
|
+
|
|
45
|
+
init():Promise<GoogleClient>;
|
|
46
|
+
|
|
47
|
+
generateAuthUrl(...args):Promise<string>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sets the credentials
|
|
51
|
+
* @param tokens
|
|
52
|
+
* @returns {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
setCredentials(tokens:object):Promise<void>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the token for the given code
|
|
58
|
+
* @param {string} code
|
|
59
|
+
* @returns {Promise<*>}
|
|
60
|
+
*/
|
|
61
|
+
getToken(code:string):Promise<object>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} parentId
|
|
65
|
+
* @param {string[]} pathSegments
|
|
66
|
+
* @param {string} parentPath
|
|
67
|
+
* @returns {Promise<DriveItemInfo[]>|null}
|
|
68
|
+
*/
|
|
69
|
+
getUncachedItemsFromSegments(parentId:string, pathSegments:string[], parentPath:string):Promise<DriveItemInfo[]>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} parentId
|
|
73
|
+
* @param {string} pathSegments
|
|
74
|
+
* @param {string} parentPath
|
|
75
|
+
* @returns {Promise<DriveItemInfo[]>|null}
|
|
76
|
+
*/
|
|
77
|
+
getDriveItemsFromSegments(parentId:string, pathSegments:string[], parentPath:string):Promise<DriveItemInfo[]>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* returns the items hierarchy for the given path and root id, starting with the given path.
|
|
81
|
+
* @param {string} parentId
|
|
82
|
+
* @param {string} path
|
|
83
|
+
* @return {DriveItemInfo[]}
|
|
84
|
+
*/
|
|
85
|
+
getDriveItemsFromSegments(parentId:string, path:string):Promise<DriveItemInfo[]>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* returns the items hierarchy for the given item, starting with the given id
|
|
89
|
+
* @param {string} fileId
|
|
90
|
+
* @param {object} roots
|
|
91
|
+
* @returns {Promise<DriveItemInfo[]>}
|
|
92
|
+
*/
|
|
93
|
+
getItemsFromId(fileId:string, roots:object):Promise<DriveItemInfo[]>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns the (cached) item for the given path or {@code null} if the item cannot be found.
|
|
97
|
+
* The item will contains a `invalidate()` method which can be used to remove it from the cache.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} parentId
|
|
100
|
+
* @param {string} path
|
|
101
|
+
* @returns {Promise<DriveItemInfo>}
|
|
102
|
+
*/
|
|
103
|
+
getItemFromPath(parentId:string, path:string):Promise<DriveItemInfo>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns an (uncached) file directly via the google api
|
|
107
|
+
* @param {string} fileId
|
|
108
|
+
* @returns {string} file data
|
|
109
|
+
*/
|
|
110
|
+
getFile(fileId:string):Promise<string>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fetches the file data from the give path. If the file with the internal id could not be
|
|
114
|
+
* fetched, the item cache is invalidated and the operation is retried. this is to support
|
|
115
|
+
* moved items.
|
|
116
|
+
* @param {string} parentId
|
|
117
|
+
* @param {string} path
|
|
118
|
+
* @param {boolean} noRetry {@code true} to avoid retry
|
|
119
|
+
* @returns {Promise<string>|null} The data of the file or {@code null} if the file does not exist
|
|
120
|
+
*/
|
|
121
|
+
getFileFromPath(parentId:string, path:string, noRetry:boolean):Promise<string>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns an (uncached) document directly via the google api
|
|
125
|
+
* @param {string} documentId
|
|
126
|
+
* @returns {object} document
|
|
127
|
+
*/
|
|
128
|
+
getDocument(documentId:string):Promise<object>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Fetches the document from the give path. If the document with the internal id could not be
|
|
132
|
+
* fetched, the item cache is invalidated and the operation is retried. this is to support
|
|
133
|
+
* moved items.
|
|
134
|
+
* @param {string} parentId
|
|
135
|
+
* @param {string} path
|
|
136
|
+
* @param {boolean} noRetry {@code true} to avoid retry
|
|
137
|
+
* @returns {Promise<object>|null} The document or {@code null} if the document does not exist
|
|
138
|
+
*/
|
|
139
|
+
getDocumentFromPath(parentId:string, path:string, noRetry:boolean):Promise<object>;
|
|
140
|
+
}
|
package/src/GoogleClient.js
CHANGED
|
@@ -9,15 +9,65 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
+
import LRU from 'lru-cache';
|
|
12
13
|
import { google } from 'googleapis';
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
editDistance, sanitizeName, splitByExtension,
|
|
16
|
+
} from '@adobe/helix-onedrive-support/utils';
|
|
17
|
+
import { StatusCodeError } from '@adobe/helix-onedrive-support';
|
|
14
18
|
import { GoogleTokenCache } from './GoogleTokenCache.js';
|
|
15
|
-
|
|
19
|
+
|
|
20
|
+
let lru = new LRU({ max: 1000, ttl: 60000 });
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef DriveItemInfo {
|
|
24
|
+
* @property {string} id
|
|
25
|
+
* @property {string} name
|
|
26
|
+
* @property {string} url
|
|
27
|
+
* @property {string} path
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Adds the last modified property if defined in item
|
|
32
|
+
* @param {DriveItemInfo} itemInfo
|
|
33
|
+
* @param item
|
|
34
|
+
* @returns {DriveItemInfo}
|
|
35
|
+
*/
|
|
36
|
+
function addLastModified(itemInfo, item) {
|
|
37
|
+
if (item.modifiedTime) {
|
|
38
|
+
// eslint-disable-next-line no-param-reassign
|
|
39
|
+
itemInfo.lastModified = Date.parse(item.modifiedTime);
|
|
40
|
+
}
|
|
41
|
+
return itemInfo;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createPathSegments(path) {
|
|
45
|
+
const pathSegments = path.split('/');
|
|
46
|
+
if (!pathSegments[0]) {
|
|
47
|
+
// if path starts with '/' the first segment is empty
|
|
48
|
+
pathSegments.shift();
|
|
49
|
+
}
|
|
50
|
+
return pathSegments;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getCacheKey(parentId, pathSegments) {
|
|
54
|
+
return `${parentId}:${pathSegments.join('/')}`;
|
|
55
|
+
}
|
|
16
56
|
|
|
17
57
|
/**
|
|
18
58
|
* Google auth client
|
|
19
59
|
*/
|
|
20
60
|
export class GoogleClient {
|
|
61
|
+
/**
|
|
62
|
+
* Sets the global item cache options. It internally creates a new LRU
|
|
63
|
+
* with the given options
|
|
64
|
+
*
|
|
65
|
+
* @param opts
|
|
66
|
+
*/
|
|
67
|
+
static setItemCacheOptions(opts) {
|
|
68
|
+
lru = new LRU(opts);
|
|
69
|
+
}
|
|
70
|
+
|
|
21
71
|
/**
|
|
22
72
|
* Returns a url for a google drive id.
|
|
23
73
|
* @param {string} id
|
|
@@ -35,39 +85,38 @@ export class GoogleClient {
|
|
|
35
85
|
* @param {ICachePlugin} plugin
|
|
36
86
|
*/
|
|
37
87
|
constructor(opts) {
|
|
38
|
-
this
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
88
|
+
Object.assign(this, {
|
|
89
|
+
log: opts.log,
|
|
90
|
+
auth: new google.auth.OAuth2(
|
|
91
|
+
opts.clientId,
|
|
92
|
+
opts.clientSecret,
|
|
93
|
+
opts.redirectUri,
|
|
94
|
+
),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (opts.cachePlugin) {
|
|
98
|
+
this.cachePlugin = opts.cachePlugin;
|
|
99
|
+
this.cache = new GoogleTokenCache(opts.cachePlugin).withLog(opts.log);
|
|
100
|
+
/// hack to capture tokens, since the emit handler is not awaited in the google client
|
|
101
|
+
const originalRefreshTokenNoCache = this.auth.refreshTokenNoCache.bind(this.auth);
|
|
102
|
+
this.auth.refreshTokenNoCache = async (...args) => {
|
|
103
|
+
const ret = await originalRefreshTokenNoCache(...args);
|
|
104
|
+
await this.cache.store(ret.tokens);
|
|
105
|
+
return ret;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
54
108
|
|
|
55
109
|
this.drive = google.drive({
|
|
56
110
|
version: 'v3',
|
|
57
111
|
auth: this.auth,
|
|
58
112
|
});
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Cached version of `getUncachedItemsFromPath`
|
|
62
|
-
*/
|
|
63
|
-
this.getDriveItemsFromPath = cache(this.getUncachedItemsFromPath.bind(this), {
|
|
64
|
-
hash: (fn, path, parentId) => `${parentId}:${path}`,
|
|
65
|
-
});
|
|
66
113
|
}
|
|
67
114
|
|
|
68
115
|
async init() {
|
|
69
|
-
|
|
70
|
-
|
|
116
|
+
if (this.cache) {
|
|
117
|
+
await this.cache.load();
|
|
118
|
+
this.auth.setCredentials(this.cache.tokens);
|
|
119
|
+
}
|
|
71
120
|
return this;
|
|
72
121
|
}
|
|
73
122
|
|
|
@@ -75,28 +124,40 @@ export class GoogleClient {
|
|
|
75
124
|
return this.auth.generateAuthUrl(...args);
|
|
76
125
|
}
|
|
77
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Sets the credentials
|
|
129
|
+
* @param tokens
|
|
130
|
+
* @returns {Promise<void>}
|
|
131
|
+
*/
|
|
78
132
|
async setCredentials(tokens) {
|
|
79
|
-
|
|
133
|
+
if (this.cache) {
|
|
134
|
+
await this.cache.store(tokens);
|
|
135
|
+
}
|
|
80
136
|
this.auth.setCredentials(tokens);
|
|
81
137
|
}
|
|
82
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Returns the token for the given code
|
|
141
|
+
* @param {string} code
|
|
142
|
+
* @returns {Promise<*>}
|
|
143
|
+
*/
|
|
83
144
|
async getToken(code) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
145
|
+
const { tokens } = await this.auth.getToken(code);
|
|
146
|
+
if (this.cache) {
|
|
147
|
+
await this.cache.store(tokens);
|
|
148
|
+
}
|
|
149
|
+
return tokens;
|
|
87
150
|
}
|
|
88
151
|
|
|
89
152
|
/**
|
|
90
|
-
* @param {Drive} drive
|
|
91
|
-
* @param {AdminContext} context
|
|
92
|
-
* @param {string} path
|
|
93
153
|
* @param {string} parentId
|
|
154
|
+
* @param {string[]} pathSegments
|
|
94
155
|
* @param {string} parentPath
|
|
95
|
-
* @returns {Promise<
|
|
156
|
+
* @returns {Promise<DriveItemInfo[]>|null}
|
|
96
157
|
*/
|
|
97
|
-
async
|
|
158
|
+
async getUncachedItemsFromSegments(parentId, pathSegments, parentPath) {
|
|
98
159
|
const { log, drive } = this;
|
|
99
|
-
const
|
|
160
|
+
const name = pathSegments.shift();
|
|
100
161
|
const [baseName, ext] = splitByExtension(name);
|
|
101
162
|
const sanitizedName = sanitizeName(baseName);
|
|
102
163
|
|
|
@@ -106,7 +167,7 @@ export class GoogleClient {
|
|
|
106
167
|
`'${parentId}' in parents`,
|
|
107
168
|
'and trashed=false',
|
|
108
169
|
// folder if path continues, sheet otherwise
|
|
109
|
-
`and mimeType ${
|
|
170
|
+
`and mimeType ${pathSegments.length ? '=' : '!='} 'application/vnd.google-apps.folder'`,
|
|
110
171
|
].join(' '),
|
|
111
172
|
fields: 'nextPageToken, files(id, name, modifiedTime)',
|
|
112
173
|
includeItemsFromAllDrives: true,
|
|
@@ -163,50 +224,83 @@ export class GoogleClient {
|
|
|
163
224
|
}
|
|
164
225
|
|
|
165
226
|
const itemPath = `${parentPath}/${sanitizedName}`;
|
|
166
|
-
const children =
|
|
227
|
+
const children = pathSegments.length
|
|
167
228
|
// eslint-disable-next-line no-use-before-define
|
|
168
|
-
? await this.
|
|
229
|
+
? await this.getDriveItemsFromSegments(item.id, pathSegments, itemPath)
|
|
169
230
|
: [];
|
|
170
231
|
|
|
171
232
|
if (!children) {
|
|
172
233
|
return null;
|
|
173
234
|
}
|
|
174
|
-
|
|
175
|
-
const pathItem = {
|
|
235
|
+
const pathItem = addLastModified({
|
|
176
236
|
name,
|
|
177
237
|
path: itemPath,
|
|
178
238
|
id: item.id,
|
|
179
|
-
|
|
180
|
-
|
|
239
|
+
}, item);
|
|
240
|
+
|
|
241
|
+
if (children.length) {
|
|
242
|
+
// add parent references not-enumerable to avoid deep structures during serialization
|
|
243
|
+
Object.defineProperty(children[children.length - 1], 'parent', {
|
|
244
|
+
enumerable: false,
|
|
245
|
+
value: pathItem,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
181
248
|
|
|
182
249
|
return [...children, pathItem];
|
|
183
250
|
}
|
|
184
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @param {string} parentId
|
|
254
|
+
* @param {string} pathSegments
|
|
255
|
+
* @param {string} parentPath
|
|
256
|
+
* @returns {Promise<DriveItemInfo[]>|null}
|
|
257
|
+
*/
|
|
258
|
+
async getDriveItemsFromSegments(parentId, pathSegments, parentPath) {
|
|
259
|
+
const key = getCacheKey(parentId, pathSegments);
|
|
260
|
+
let items = lru.get(key);
|
|
261
|
+
if (items) {
|
|
262
|
+
return items;
|
|
263
|
+
}
|
|
264
|
+
items = await this.getUncachedItemsFromSegments(parentId, pathSegments, parentPath);
|
|
265
|
+
if (items) {
|
|
266
|
+
lru.set(key, items);
|
|
267
|
+
// add invalidation function as not-enumerable to keep compatible objects
|
|
268
|
+
Object.defineProperty(items[items.length - 1], 'invalidate', {
|
|
269
|
+
enumerable: false,
|
|
270
|
+
value() {
|
|
271
|
+
lru.delete(key);
|
|
272
|
+
this.parent?.invalidate();
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return items;
|
|
277
|
+
}
|
|
278
|
+
|
|
185
279
|
/**
|
|
186
280
|
* returns the items hierarchy for the given path and root id, starting with the given path.
|
|
187
|
-
* @param
|
|
188
|
-
* @param path
|
|
189
|
-
* @
|
|
281
|
+
* @param {string} parentId
|
|
282
|
+
* @param {string} path
|
|
283
|
+
* @return {DriveItemInfo[]}
|
|
190
284
|
*/
|
|
191
|
-
async getItemsFromPath(
|
|
192
|
-
const
|
|
285
|
+
async getItemsFromPath(parentId, path) {
|
|
286
|
+
const segs = createPathSegments(path);
|
|
287
|
+
const result = await this.getDriveItemsFromSegments(parentId, segs, '');
|
|
193
288
|
if (!result) {
|
|
194
|
-
return
|
|
289
|
+
return [];
|
|
195
290
|
}
|
|
196
291
|
return [...result, {
|
|
197
292
|
name: '',
|
|
198
293
|
path: '/',
|
|
199
|
-
id:
|
|
294
|
+
id: parentId,
|
|
200
295
|
}];
|
|
201
296
|
}
|
|
202
297
|
|
|
203
298
|
/**
|
|
204
299
|
* returns the items hierarchy for the given item, starting with the given id
|
|
205
|
-
* @param {AdminContext} context
|
|
206
|
-
* @param {Drive} drive
|
|
207
300
|
* @param {string} fileId
|
|
208
301
|
* @param {object} roots
|
|
209
|
-
* @
|
|
302
|
+
* @private
|
|
303
|
+
* @returns {Promise<DriveItemInfo[]>}
|
|
210
304
|
*/
|
|
211
305
|
async getItems(fileId, roots) {
|
|
212
306
|
const { log } = this;
|
|
@@ -223,62 +317,170 @@ export class GoogleClient {
|
|
|
223
317
|
const root = roots[fileId];
|
|
224
318
|
if (root) {
|
|
225
319
|
// stop at mount root
|
|
226
|
-
return [{
|
|
320
|
+
return [addLastModified({
|
|
227
321
|
id: fileId,
|
|
228
322
|
name: '',
|
|
229
323
|
path: root,
|
|
230
|
-
|
|
231
|
-
}];
|
|
324
|
+
}, data)];
|
|
232
325
|
}
|
|
233
326
|
|
|
234
327
|
const parentId = data.parents ? data.parents[0] : '';
|
|
235
328
|
if (!parentId) {
|
|
236
329
|
// outside mountpoint
|
|
237
|
-
return [{
|
|
330
|
+
return [addLastModified({
|
|
238
331
|
id: fileId,
|
|
239
332
|
name: data.name,
|
|
240
333
|
path: `/root:/${data.name}`,
|
|
241
|
-
|
|
242
|
-
}];
|
|
334
|
+
}, data)];
|
|
243
335
|
}
|
|
244
336
|
|
|
245
337
|
const ancestors = await this.getItems(data.parents[0], roots);
|
|
246
338
|
const parentPath = ancestors[0].path.replace(/\/+$/, '');
|
|
247
|
-
ancestors.unshift({
|
|
339
|
+
ancestors.unshift(addLastModified({
|
|
248
340
|
id: fileId,
|
|
249
341
|
name: data.name,
|
|
250
342
|
path: `${parentPath}/${data.name}`,
|
|
251
|
-
|
|
252
|
-
});
|
|
343
|
+
}, data));
|
|
253
344
|
return ancestors;
|
|
254
345
|
}
|
|
255
346
|
|
|
256
347
|
/**
|
|
257
|
-
* @param {AdminContext} context
|
|
258
348
|
* @param {string} fileId
|
|
259
349
|
* @param {object} roots
|
|
260
|
-
* @returns {Promise<
|
|
350
|
+
* @returns {Promise<DriveItemInfo[]>}
|
|
261
351
|
*/
|
|
262
352
|
async getItemsFromId(fileId, roots) {
|
|
263
353
|
const { log } = this;
|
|
264
354
|
try {
|
|
265
355
|
return await this.getItems(fileId, roots);
|
|
266
356
|
} catch (e) {
|
|
357
|
+
if (e.response && e.response.status === 404) {
|
|
358
|
+
log.warn(`unable to get items for ${fileId}. Not found`);
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
267
361
|
log.warn(`unable to get items for ${fileId}. ${e}`);
|
|
268
|
-
|
|
362
|
+
throw e;
|
|
269
363
|
}
|
|
270
364
|
}
|
|
271
365
|
|
|
272
366
|
/**
|
|
273
|
-
* @
|
|
367
|
+
* Returns the (cached) item for the given path or {@code null} if the item cannot be found.
|
|
368
|
+
* The item will contains a `invalidate()` method which can be used to remove it from the cache.
|
|
369
|
+
*
|
|
370
|
+
* @param {string} parentId
|
|
371
|
+
* @param {string} path
|
|
372
|
+
* @returns {Promise<DriveItemInfo>}
|
|
373
|
+
*/
|
|
374
|
+
async getItemFromPath(parentId, path) {
|
|
375
|
+
const segs = createPathSegments(path);
|
|
376
|
+
const items = await this.getDriveItemsFromSegments(parentId, segs, '');
|
|
377
|
+
const item = items?.[0];
|
|
378
|
+
if (!item) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
return item;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Returns an (uncached) file directly via the google api
|
|
274
386
|
* @param {string} fileId
|
|
275
387
|
* @returns {string} file data
|
|
276
388
|
*/
|
|
277
389
|
async getFile(fileId) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
390
|
+
try {
|
|
391
|
+
const res = await this.drive.files.get({
|
|
392
|
+
fileId,
|
|
393
|
+
alt: 'media',
|
|
394
|
+
});
|
|
395
|
+
return res.data;
|
|
396
|
+
} catch (e) {
|
|
397
|
+
if (e.response && e.response.status === 404) {
|
|
398
|
+
throw new StatusCodeError(`Not Found: ${fileId}`, 404);
|
|
399
|
+
}
|
|
400
|
+
throw e;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Fetches the file data from the give path. If the file with the internal id could not be
|
|
406
|
+
* fetched, the item cache is invalidated and the operation is retried. this is to support
|
|
407
|
+
* moved items.
|
|
408
|
+
* @param {string} parentId
|
|
409
|
+
* @param {string} path
|
|
410
|
+
* @param {boolean} noRetry {@code true} to avoid retry
|
|
411
|
+
* @returns {Promise<string>|null} The data of the file or {@code null} if the file does not exist
|
|
412
|
+
*/
|
|
413
|
+
async getFileFromPath(parentId, path, noRetry) {
|
|
414
|
+
const item = await this.getItemFromPath(parentId, path);
|
|
415
|
+
if (!item) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
// noinspection ES6RedundantAwait (need to catch exception)
|
|
420
|
+
return await this.getFile(item.id);
|
|
421
|
+
} catch (e) {
|
|
422
|
+
if (e.statusCode === 404) {
|
|
423
|
+
if (noRetry) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
this.log.info(`file ${item.id} does not exist - invalidating cache and retry`);
|
|
427
|
+
item.invalidate();
|
|
428
|
+
return this.getFileFromPath(parentId, path, true);
|
|
429
|
+
} else {
|
|
430
|
+
throw e;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Returns an (uncached) document directly via the google api
|
|
437
|
+
* @param {string} documentId
|
|
438
|
+
* @returns {object} document
|
|
439
|
+
*/
|
|
440
|
+
async getDocument(documentId) {
|
|
441
|
+
const docs = google.docs({
|
|
442
|
+
version: 'v1',
|
|
443
|
+
auth: this.auth,
|
|
281
444
|
});
|
|
282
|
-
|
|
445
|
+
try {
|
|
446
|
+
const res = await docs.documents.get({ documentId });
|
|
447
|
+
return res.data;
|
|
448
|
+
} catch (e) {
|
|
449
|
+
if (e.response && e.response.status === 404) {
|
|
450
|
+
throw new StatusCodeError(`Not Found: ${documentId}`, 404);
|
|
451
|
+
}
|
|
452
|
+
throw e;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Fetches the document from the give path. If the document with the internal id could not be
|
|
458
|
+
* fetched, the item cache is invalidated and the operation is retried. this is to support
|
|
459
|
+
* moved items.
|
|
460
|
+
* @param {string} parentId
|
|
461
|
+
* @param {string} path
|
|
462
|
+
* @param {boolean} noRetry {@code true} to avoid retry
|
|
463
|
+
* @returns {Promise<object>|null} The document or {@code null} if the document does not exist
|
|
464
|
+
*/
|
|
465
|
+
async getDocumentFromPath(parentId, path, noRetry) {
|
|
466
|
+
const item = await this.getItemFromPath(parentId, path);
|
|
467
|
+
if (!item) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
// noinspection ES6RedundantAwait (need to catch exception)
|
|
472
|
+
return await this.getDocument(item.id);
|
|
473
|
+
} catch (e) {
|
|
474
|
+
if (e.statusCode === 404) {
|
|
475
|
+
if (noRetry) {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
this.log.info(`document ${item.id} does not exist - invalidating cache and retry`);
|
|
479
|
+
item.invalidate();
|
|
480
|
+
return this.getDocumentFromPath(parentId, path, true);
|
|
481
|
+
} else {
|
|
482
|
+
throw e;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
283
485
|
}
|
|
284
486
|
}
|
package/src/cache.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
-
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*
|
|
7
|
-
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
-
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
-
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
-
* governing permissions and limitations under the License.
|
|
11
|
-
*/
|
|
12
|
-
import LRU from 'lru-cache';
|
|
13
|
-
|
|
14
|
-
let lru = new LRU({ max: 1000, ttl: 60000 });
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Returns a memoized version of the function `fn`.
|
|
18
|
-
* @param {function} fn a function to memoize
|
|
19
|
-
* @param {object} opts caching options
|
|
20
|
-
* @param {function} opts.hash a hash function to build a cache key.
|
|
21
|
-
* The hash function will be called with the `fn` and a list of
|
|
22
|
-
* it's arguments. A good hashing function will discard irrelevant
|
|
23
|
-
* arguments and join the resulting arguments
|
|
24
|
-
* @param {function} opts.cacheresult a predicate function that will be called
|
|
25
|
-
* with the result of the function execution. Only when this function returns
|
|
26
|
-
* `true` will the result be cached. The default is that all results are
|
|
27
|
-
* cached.
|
|
28
|
-
* @param {function} opts.cacheerror a predicate function that will be called
|
|
29
|
-
* with the error that the function execution throws. Only when this
|
|
30
|
-
* function returns `true` will the error be cached. The default is that
|
|
31
|
-
* errors are never cached, so that each new invocation will be uncached.
|
|
32
|
-
*/
|
|
33
|
-
export default function cache(fn, opts = {}) {
|
|
34
|
-
const {
|
|
35
|
-
hash = (...args) => args.join(),
|
|
36
|
-
cacheresult = () => true,
|
|
37
|
-
cacheerror = () => false,
|
|
38
|
-
} = opts;
|
|
39
|
-
return async function cached(...args) {
|
|
40
|
-
const key = hash(fn, ...args);
|
|
41
|
-
const entry = lru.get(key);
|
|
42
|
-
if (entry) {
|
|
43
|
-
if (entry.err) {
|
|
44
|
-
throw entry.err;
|
|
45
|
-
}
|
|
46
|
-
return entry.ok;
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
// invoke the function
|
|
50
|
-
const result = await fn(...args);
|
|
51
|
-
if (cacheresult(result)) {
|
|
52
|
-
// store the result under ok if permitted
|
|
53
|
-
lru.set(key, { ok: result });
|
|
54
|
-
}
|
|
55
|
-
// and return the result
|
|
56
|
-
return result;
|
|
57
|
-
} catch (err) {
|
|
58
|
-
if (cacheerror(err)) {
|
|
59
|
-
// store the error under err if permitted
|
|
60
|
-
lru.set(key, { err });
|
|
61
|
-
}
|
|
62
|
-
// and throw the error
|
|
63
|
-
throw err;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Resets the QuickLRU cache with new options. Existing cache entries
|
|
70
|
-
* will be cleared.
|
|
71
|
-
* @param {object} opts options
|
|
72
|
-
* @param {integer} opts.maxSize maximum size of the cache
|
|
73
|
-
*/
|
|
74
|
-
cache.options = (opts) => {
|
|
75
|
-
lru = new LRU(opts);
|
|
76
|
-
|
|
77
|
-
return cache;
|
|
78
|
-
};
|