incyclist-services 1.7.139 → 1.8.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/lib/cjs/activities/active-rides/service.js +1 -1
- package/lib/cjs/activities/page/service.js +211 -7
- package/lib/cjs/api/bindings/index.js +1 -0
- package/lib/cjs/fileaccess/externalFiles.js +39 -32
- package/lib/cjs/fileaccess/service.js +42 -28
- package/lib/cjs/fileaccess/utils.js +6 -2
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/ride/page/service.js +168 -8
- package/lib/cjs/routes/index.js +1 -0
- package/lib/cjs/routes/library/service.js +118 -22
- package/lib/cjs/routes/list/cards/RouteCard.js +19 -2
- package/lib/cjs/routes/list/service.js +82 -48
- package/lib/cjs/routes/page/service.js +318 -12
- package/lib/cjs/routes/previews/store.js +9 -10
- package/lib/cjs/routes/video-availability/journal.js +2 -2
- package/lib/cjs/routes/video-availability/pageActions.js +19 -3
- package/lib/cjs/routes/video-availability/service.js +29 -20
- package/lib/esm/activities/active-rides/service.js +1 -1
- package/lib/esm/activities/page/service.js +211 -7
- package/lib/esm/api/bindings/index.js +1 -0
- package/lib/esm/fileaccess/externalFiles.js +39 -32
- package/lib/esm/fileaccess/service.js +42 -28
- package/lib/esm/fileaccess/utils.js +6 -2
- package/lib/esm/index.js +1 -0
- package/lib/esm/ride/page/service.js +168 -8
- package/lib/esm/routes/index.js +1 -0
- package/lib/esm/routes/library/service.js +118 -22
- package/lib/esm/routes/list/cards/RouteCard.js +19 -2
- package/lib/esm/routes/list/service.js +82 -48
- package/lib/esm/routes/page/service.js +318 -12
- package/lib/esm/routes/previews/store.js +9 -10
- package/lib/esm/routes/video-availability/journal.js +2 -2
- package/lib/esm/routes/video-availability/pageActions.js +19 -3
- package/lib/esm/routes/video-availability/service.js +29 -20
- package/lib/types/activities/active-rides/types.d.ts +1 -0
- package/lib/types/activities/page/service.d.ts +30 -2
- package/lib/types/activities/page/types.d.ts +22 -2
- package/lib/types/api/bindings/index.d.ts +2 -0
- package/lib/types/api/types.d.ts +1 -0
- package/lib/types/api/ui/index.d.ts +6 -1
- package/lib/types/fileaccess/externalFiles.d.ts +2 -0
- package/lib/types/fileaccess/service.d.ts +4 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/ride/page/service.d.ts +27 -0
- package/lib/types/ride/page/types.d.ts +5 -0
- package/lib/types/routes/base/types/index.d.ts +1 -0
- package/lib/types/routes/index.d.ts +1 -0
- package/lib/types/routes/library/service.d.ts +7 -0
- package/lib/types/routes/library/types.d.ts +8 -0
- package/lib/types/routes/list/cards/RouteCard.d.ts +4 -0
- package/lib/types/routes/list/cards/types.d.ts +2 -0
- package/lib/types/routes/list/service.d.ts +2 -0
- package/lib/types/routes/page/service.d.ts +46 -1
- package/lib/types/routes/page/types.d.ts +59 -1
- package/lib/types/routes/types.d.ts +1 -0
- package/lib/types/routes/video-availability/pageActions.d.ts +2 -1
- package/lib/types/routes/video-availability/service.d.ts +3 -1
- package/lib/types/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -720,7 +720,7 @@ let ActiveRidesService = (() => {
|
|
|
720
720
|
if (!this.current || !this.isOnline)
|
|
721
721
|
return;
|
|
722
722
|
const topic = `incyclist/activity/${this.session}/${this.getRouteHash()}/update`;
|
|
723
|
-
this.getMessageQueue().sendMessage(topic, payload);
|
|
723
|
+
this.getMessageQueue().sendMessage(topic, { ...payload, ts: Date.now() });
|
|
724
724
|
}
|
|
725
725
|
async onActivityUpdateEvent(session, payload) {
|
|
726
726
|
try {
|
|
@@ -42,6 +42,9 @@ const list_1 = require("../list");
|
|
|
42
42
|
const sleep_1 = require("../../utils/sleep");
|
|
43
43
|
const workouts_1 = require("../../workouts");
|
|
44
44
|
const devices_1 = require("../../devices");
|
|
45
|
+
const service_1 = require("../../fileaccess/service");
|
|
46
|
+
const service_2 = require("../../routes/video-availability/service");
|
|
47
|
+
const pageActions_1 = require("../../routes/video-availability/pageActions");
|
|
45
48
|
let ActivitiesPageService = (() => {
|
|
46
49
|
let _classDecorators = [decorators_1.Singleton];
|
|
47
50
|
let _classDescriptor;
|
|
@@ -52,6 +55,9 @@ let ActivitiesPageService = (() => {
|
|
|
52
55
|
let _getActivityList_decorators;
|
|
53
56
|
let _getWorkoutList_decorators;
|
|
54
57
|
let _getDevicePairing_decorators;
|
|
58
|
+
let _getFolderAccess_decorators;
|
|
59
|
+
let _getAvailability_decorators;
|
|
60
|
+
let _getVideoActions_decorators;
|
|
55
61
|
var ActivitiesPageService = class extends _classSuper {
|
|
56
62
|
static { _classThis = this; }
|
|
57
63
|
static {
|
|
@@ -59,9 +65,15 @@ let ActivitiesPageService = (() => {
|
|
|
59
65
|
_getActivityList_decorators = [decorators_1.Injectable];
|
|
60
66
|
_getWorkoutList_decorators = [decorators_1.Injectable];
|
|
61
67
|
_getDevicePairing_decorators = [decorators_1.Injectable];
|
|
68
|
+
_getFolderAccess_decorators = [decorators_1.Injectable];
|
|
69
|
+
_getAvailability_decorators = [decorators_1.Injectable];
|
|
70
|
+
_getVideoActions_decorators = [decorators_1.Injectable];
|
|
62
71
|
__esDecorate(this, null, _getActivityList_decorators, { kind: "method", name: "getActivityList", static: false, private: false, access: { has: obj => "getActivityList" in obj, get: obj => obj.getActivityList }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
63
72
|
__esDecorate(this, null, _getWorkoutList_decorators, { kind: "method", name: "getWorkoutList", static: false, private: false, access: { has: obj => "getWorkoutList" in obj, get: obj => obj.getWorkoutList }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
64
73
|
__esDecorate(this, null, _getDevicePairing_decorators, { kind: "method", name: "getDevicePairing", static: false, private: false, access: { has: obj => "getDevicePairing" in obj, get: obj => obj.getDevicePairing }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
74
|
+
__esDecorate(this, null, _getFolderAccess_decorators, { kind: "method", name: "getFolderAccess", static: false, private: false, access: { has: obj => "getFolderAccess" in obj, get: obj => obj.getFolderAccess }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
75
|
+
__esDecorate(this, null, _getAvailability_decorators, { kind: "method", name: "getAvailability", static: false, private: false, access: { has: obj => "getAvailability" in obj, get: obj => obj.getAvailability }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
76
|
+
__esDecorate(this, null, _getVideoActions_decorators, { kind: "method", name: "getVideoActions", static: false, private: false, access: { has: obj => "getVideoActions" in obj, get: obj => obj.getVideoActions }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
65
77
|
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
66
78
|
ActivitiesPageService = _classThis = _classDescriptor.value;
|
|
67
79
|
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
@@ -71,6 +83,9 @@ let ActivitiesPageService = (() => {
|
|
|
71
83
|
listState;
|
|
72
84
|
detailActivityId;
|
|
73
85
|
isOpen = false;
|
|
86
|
+
rideAgainRoute;
|
|
87
|
+
rideAgainCheck = null;
|
|
88
|
+
rideAgainVideoUpdateHandler = this.onRideAgainVideoUpdate.bind(this);
|
|
74
89
|
constructor() {
|
|
75
90
|
super('ActivitiesPage');
|
|
76
91
|
}
|
|
@@ -78,6 +93,7 @@ let ActivitiesPageService = (() => {
|
|
|
78
93
|
if (this.isOpen) {
|
|
79
94
|
this.getActivityList().closeList();
|
|
80
95
|
this.stopEventListener();
|
|
96
|
+
this.closeRideAgainCheck();
|
|
81
97
|
}
|
|
82
98
|
try {
|
|
83
99
|
this.logEvent({ message: 'page shown', page: 'Activities' });
|
|
@@ -102,6 +118,7 @@ let ActivitiesPageService = (() => {
|
|
|
102
118
|
this.logEvent({ message: 'page closed', page: 'Activities' });
|
|
103
119
|
this.getActivityList().closeList();
|
|
104
120
|
this.stopEventListener();
|
|
121
|
+
this.closeRideAgainCheck();
|
|
105
122
|
this.isOpen = false;
|
|
106
123
|
super.closePage();
|
|
107
124
|
}
|
|
@@ -121,6 +138,7 @@ let ActivitiesPageService = (() => {
|
|
|
121
138
|
resumePage() {
|
|
122
139
|
try {
|
|
123
140
|
this.startEventListener();
|
|
141
|
+
this.getAvailability().onForeground().catch(err => this.logError(err, 'resumePage'));
|
|
124
142
|
return super.resumePage();
|
|
125
143
|
}
|
|
126
144
|
catch (err) {
|
|
@@ -131,7 +149,8 @@ let ActivitiesPageService = (() => {
|
|
|
131
149
|
const props = {
|
|
132
150
|
loading: this.listState.loading,
|
|
133
151
|
activities: this.listState.activities,
|
|
134
|
-
detailActivityId: this.detailActivityId
|
|
152
|
+
detailActivityId: this.detailActivityId,
|
|
153
|
+
rideAgainCheck: this.rideAgainCheck
|
|
135
154
|
};
|
|
136
155
|
return props;
|
|
137
156
|
}
|
|
@@ -165,16 +184,128 @@ let ActivitiesPageService = (() => {
|
|
|
165
184
|
return { activityId, attachedWorkout: null };
|
|
166
185
|
}
|
|
167
186
|
}
|
|
168
|
-
onRideAgain(route) {
|
|
187
|
+
async onRideAgain(route) {
|
|
169
188
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
if (!route?.id || !this.getVideoActions().isSupported()) {
|
|
190
|
+
this.navigateToRide(route);
|
|
191
|
+
return 'started';
|
|
192
|
+
}
|
|
193
|
+
let status;
|
|
194
|
+
try {
|
|
195
|
+
await this.getFolderAccess().activateAll();
|
|
196
|
+
status = await this.getAvailability().refresh(route.id);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
this.logError(err, 'onRideAgain');
|
|
200
|
+
this.navigateToRide(route);
|
|
201
|
+
return 'started';
|
|
202
|
+
}
|
|
203
|
+
if (status.state === 'ready' || status.state === 'unknown') {
|
|
204
|
+
this.closeRideAgainCheck();
|
|
205
|
+
this.navigateToRide(route);
|
|
206
|
+
return 'started';
|
|
207
|
+
}
|
|
208
|
+
this.openRideAgainCheck(route);
|
|
209
|
+
return 'blocked';
|
|
175
210
|
}
|
|
176
211
|
catch (err) {
|
|
177
212
|
this.logError(err, 'onRideAgain');
|
|
213
|
+
return 'started';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
onRideAgainCheckClosed() {
|
|
217
|
+
try {
|
|
218
|
+
this.closeRideAgainCheck();
|
|
219
|
+
this.updatePageDisplay();
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
this.logError(err, 'onRideAgainCheckClosed');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
onRideAgainCheckStart() {
|
|
226
|
+
const route = this.rideAgainRoute;
|
|
227
|
+
if (!route)
|
|
228
|
+
return Promise.resolve('blocked');
|
|
229
|
+
return this.onRideAgain(route);
|
|
230
|
+
}
|
|
231
|
+
onVideoDownloadPressed(routeId) {
|
|
232
|
+
try {
|
|
233
|
+
this.getVideoActions().onVideoDownloadPressed(routeId);
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
this.logError(err, 'onVideoDownloadPressed');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
onVideoDownloadConfirmed(routeId, choice) {
|
|
240
|
+
try {
|
|
241
|
+
this.getVideoActions().onVideoDownloadConfirmed(routeId, choice);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
this.logError(err, 'onVideoDownloadConfirmed');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
onVideoDownloadDismissed(routeId) {
|
|
248
|
+
try {
|
|
249
|
+
this.getVideoActions().onVideoDownloadDismissed(routeId);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
this.logError(err, 'onVideoDownloadDismissed');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
onVideoStop(routeId) {
|
|
256
|
+
try {
|
|
257
|
+
this.getVideoActions().onVideoStop(routeId);
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
this.logError(err, 'onVideoStop');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
onVideoRetry(routeId) {
|
|
264
|
+
try {
|
|
265
|
+
this.getVideoActions().onVideoRetry(routeId);
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
this.logError(err, 'onVideoRetry');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
onVideoKeepInstead(routeId) {
|
|
272
|
+
try {
|
|
273
|
+
this.getVideoActions().onVideoKeepInstead(routeId);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
this.logError(err, 'onVideoKeepInstead');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
onVideoRemovePressed(routeId) {
|
|
280
|
+
try {
|
|
281
|
+
this.getVideoActions().onVideoRemovePressed(routeId);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
this.logError(err, 'onVideoRemovePressed');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
onVideoRemoveConfirmed(routeId) {
|
|
288
|
+
try {
|
|
289
|
+
this.getVideoActions().onVideoRemoveConfirmed(routeId);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
this.logError(err, 'onVideoRemoveConfirmed');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
onVideoRemoveDismissed(routeId) {
|
|
296
|
+
try {
|
|
297
|
+
this.getVideoActions().onVideoRemoveDismissed(routeId);
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
this.logError(err, 'onVideoRemoveDismissed');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async onConfirmAccess(routeId) {
|
|
304
|
+
try {
|
|
305
|
+
await this.getVideoActions().onConfirmAccess(routeId);
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
this.logError(err, 'onConfirmAccess');
|
|
178
309
|
}
|
|
179
310
|
}
|
|
180
311
|
onClearWorkoutSelection() {
|
|
@@ -198,6 +329,70 @@ let ActivitiesPageService = (() => {
|
|
|
198
329
|
updatePageDisplay() {
|
|
199
330
|
this.getPageObserver()?.emit('page-update');
|
|
200
331
|
}
|
|
332
|
+
navigateToRide(route) {
|
|
333
|
+
const pairing = this.getDevicePairing();
|
|
334
|
+
const readyToStart = pairing.isReadyToStart();
|
|
335
|
+
this.logEvent({ message: 'Attempting to start a ride again', id: route?.id, title: route?.title, readyToStart });
|
|
336
|
+
const next = readyToStart ? '/rideDeviceOK' : '/pairingStart';
|
|
337
|
+
this.moveTo(next);
|
|
338
|
+
}
|
|
339
|
+
openRideAgainCheck(route) {
|
|
340
|
+
this.rideAgainRoute = route;
|
|
341
|
+
this.rideAgainCheck = {
|
|
342
|
+
routeId: route.id,
|
|
343
|
+
routeTitle: route.title ?? '',
|
|
344
|
+
video: this.getVideoActions().getDisplayProps(route.id),
|
|
345
|
+
downloadedWhileOpen: false
|
|
346
|
+
};
|
|
347
|
+
this.subscribeToRideAgainVideoUpdates();
|
|
348
|
+
this.updatePageDisplay();
|
|
349
|
+
}
|
|
350
|
+
closeRideAgainCheck() {
|
|
351
|
+
if (!this.rideAgainCheck && !this.rideAgainRoute)
|
|
352
|
+
return;
|
|
353
|
+
this.unsubscribeFromRideAgainVideoUpdates();
|
|
354
|
+
this.rideAgainRoute = undefined;
|
|
355
|
+
this.rideAgainCheck = null;
|
|
356
|
+
}
|
|
357
|
+
subscribeToRideAgainVideoUpdates() {
|
|
358
|
+
try {
|
|
359
|
+
this.unsubscribeFromRideAgainVideoUpdates();
|
|
360
|
+
this.getAvailability().on('route-video-update', this.rideAgainVideoUpdateHandler);
|
|
361
|
+
this.getVideoActions().on('route-video-actions-update', this.rideAgainVideoUpdateHandler);
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
this.logError(err, 'subscribeToRideAgainVideoUpdates');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
unsubscribeFromRideAgainVideoUpdates() {
|
|
368
|
+
try {
|
|
369
|
+
this.getAvailability().off('route-video-update', this.rideAgainVideoUpdateHandler);
|
|
370
|
+
this.getVideoActions().off('route-video-actions-update', this.rideAgainVideoUpdateHandler);
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
this.logError(err, 'unsubscribeFromRideAgainVideoUpdates');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
onRideAgainVideoUpdate(routeId) {
|
|
377
|
+
try {
|
|
378
|
+
const route = this.rideAgainRoute;
|
|
379
|
+
if (!route || !this.rideAgainCheck)
|
|
380
|
+
return;
|
|
381
|
+
if (routeId && routeId !== route.id)
|
|
382
|
+
return;
|
|
383
|
+
const status = this.getAvailability().getStatus(route.id);
|
|
384
|
+
const nowReady = status.state === 'ready' || status.state === 'unknown';
|
|
385
|
+
this.rideAgainCheck = {
|
|
386
|
+
...this.rideAgainCheck,
|
|
387
|
+
video: this.getVideoActions().getDisplayProps(route.id),
|
|
388
|
+
downloadedWhileOpen: this.rideAgainCheck.downloadedWhileOpen || nowReady
|
|
389
|
+
};
|
|
390
|
+
this.updatePageDisplay();
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
this.logError(err, 'onRideAgainVideoUpdate');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
201
396
|
onStateUpdate(state) {
|
|
202
397
|
if (state)
|
|
203
398
|
this.listState = state;
|
|
@@ -226,6 +421,15 @@ let ActivitiesPageService = (() => {
|
|
|
226
421
|
getDevicePairing() {
|
|
227
422
|
return (0, devices_1.useDevicePairing)();
|
|
228
423
|
}
|
|
424
|
+
getFolderAccess() {
|
|
425
|
+
return (0, service_1.useFolderAccess)();
|
|
426
|
+
}
|
|
427
|
+
getAvailability() {
|
|
428
|
+
return (0, service_2.useRouteVideoAvailability)();
|
|
429
|
+
}
|
|
430
|
+
getVideoActions() {
|
|
431
|
+
return (0, pageActions_1.useRouteVideoPageActions)();
|
|
432
|
+
}
|
|
229
433
|
};
|
|
230
434
|
return ActivitiesPageService = _classThis;
|
|
231
435
|
})();
|
|
@@ -117,20 +117,9 @@ let ExternalFileService = (() => {
|
|
|
117
117
|
async ensureLocal(path, opts) {
|
|
118
118
|
const start = Date.now();
|
|
119
119
|
const binding = this.getBindings()?.fileAccess;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return { ok: true, downloaded: false, waitedMs: 0 };
|
|
124
|
-
let location;
|
|
125
|
-
try {
|
|
126
|
-
location = binding.classifyLocation(path);
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
this.logError(err, 'ensureLocal:classifyLocation');
|
|
130
|
-
return { ok: true, downloaded: false, waitedMs: 0 };
|
|
131
|
-
}
|
|
132
|
-
if (location !== 'icloud')
|
|
133
|
-
return { ok: true, downloaded: false, waitedMs: 0 };
|
|
120
|
+
const skip = this.resolveSkip(path, binding);
|
|
121
|
+
if (skip)
|
|
122
|
+
return skip;
|
|
134
123
|
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
135
124
|
const isCancelled = opts?.isCancelled ?? (() => false);
|
|
136
125
|
try {
|
|
@@ -139,7 +128,7 @@ let ExternalFileService = (() => {
|
|
|
139
128
|
return { ok: false, reason: 'access-lost' };
|
|
140
129
|
if (probe.state === 'not-found')
|
|
141
130
|
return { ok: false, reason: 'not-found' };
|
|
142
|
-
|
|
131
|
+
const availability = await binding.getAvailability(path);
|
|
143
132
|
if (this.isLocallyAvailable(availability))
|
|
144
133
|
return { ok: true, downloaded: false, waitedMs: Date.now() - start };
|
|
145
134
|
if (availability.downloadError)
|
|
@@ -149,29 +138,47 @@ let ExternalFileService = (() => {
|
|
|
149
138
|
if (isCancelled())
|
|
150
139
|
return { ok: false, reason: 'cancelled' };
|
|
151
140
|
await binding.startDownload(path);
|
|
152
|
-
|
|
153
|
-
for (;;) {
|
|
154
|
-
const elapsed = Date.now() - start;
|
|
155
|
-
if (elapsed >= timeoutMs)
|
|
156
|
-
return { ok: false, reason: 'timeout' };
|
|
157
|
-
if (isCancelled())
|
|
158
|
-
return { ok: false, reason: 'cancelled' };
|
|
159
|
-
if (this.isOffline())
|
|
160
|
-
return { ok: false, reason: 'offline' };
|
|
161
|
-
await (0, sleep_1.sleep)(Math.max(0, Math.min(interval, timeoutMs - elapsed)));
|
|
162
|
-
interval = BACKOFF_POLL_INTERVAL_MS;
|
|
163
|
-
availability = await binding.getAvailability(path);
|
|
164
|
-
if (this.isLocallyAvailable(availability))
|
|
165
|
-
return { ok: true, downloaded: true, waitedMs: Date.now() - start };
|
|
166
|
-
if (availability.downloadError)
|
|
167
|
-
return this.downloadFailedResult(availability.downloadError);
|
|
168
|
-
}
|
|
141
|
+
return await this.pollUntilAvailable(binding, path, start, timeoutMs, isCancelled);
|
|
169
142
|
}
|
|
170
143
|
catch (err) {
|
|
171
144
|
this.logError(err, 'ensureLocal');
|
|
172
145
|
return { ok: false, reason: 'download-failed' };
|
|
173
146
|
}
|
|
174
147
|
}
|
|
148
|
+
resolveSkip(path, binding) {
|
|
149
|
+
if (!path || !binding?.isSupported())
|
|
150
|
+
return { ok: true, downloaded: false, waitedMs: 0 };
|
|
151
|
+
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('content://'))
|
|
152
|
+
return { ok: true, downloaded: false, waitedMs: 0 };
|
|
153
|
+
let location;
|
|
154
|
+
try {
|
|
155
|
+
location = binding.classifyLocation(path);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
this.logError(err, 'ensureLocal:classifyLocation');
|
|
159
|
+
return { ok: true, downloaded: false, waitedMs: 0 };
|
|
160
|
+
}
|
|
161
|
+
return location !== 'icloud' ? { ok: true, downloaded: false, waitedMs: 0 } : undefined;
|
|
162
|
+
}
|
|
163
|
+
async pollUntilAvailable(binding, path, start, timeoutMs, isCancelled) {
|
|
164
|
+
let interval = INITIAL_POLL_INTERVAL_MS;
|
|
165
|
+
for (;;) {
|
|
166
|
+
const elapsed = Date.now() - start;
|
|
167
|
+
if (elapsed >= timeoutMs)
|
|
168
|
+
return { ok: false, reason: 'timeout' };
|
|
169
|
+
if (isCancelled())
|
|
170
|
+
return { ok: false, reason: 'cancelled' };
|
|
171
|
+
if (this.isOffline())
|
|
172
|
+
return { ok: false, reason: 'offline' };
|
|
173
|
+
await (0, sleep_1.sleep)(Math.max(0, Math.min(interval, timeoutMs - elapsed)));
|
|
174
|
+
interval = BACKOFF_POLL_INTERVAL_MS;
|
|
175
|
+
const availability = await binding.getAvailability(path);
|
|
176
|
+
if (this.isLocallyAvailable(availability))
|
|
177
|
+
return { ok: true, downloaded: true, waitedMs: Date.now() - start };
|
|
178
|
+
if (availability.downloadError)
|
|
179
|
+
return this.downloadFailedResult(availability.downloadError);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
175
182
|
isLocallyAvailable(availability) {
|
|
176
183
|
return !availability.isUbiquitous || availability.downloadStatus === 'current' || availability.downloadStatus === 'downloaded';
|
|
177
184
|
}
|
|
@@ -104,20 +104,7 @@ let FolderAccessService = (() => {
|
|
|
104
104
|
const id = existing?.id ?? (0, uuid_1.v4)();
|
|
105
105
|
await this.releaseScope(id);
|
|
106
106
|
const location = this.classify(canonical);
|
|
107
|
-
|
|
108
|
-
let source = 'pick';
|
|
109
|
-
let token = grant;
|
|
110
|
-
if (!activation.ok) {
|
|
111
|
-
const captured = await this.tryCapture(canonical);
|
|
112
|
-
if (captured) {
|
|
113
|
-
const capturedActivation = await this.tryActivate(id, captured);
|
|
114
|
-
if (capturedActivation.ok) {
|
|
115
|
-
activation = capturedActivation;
|
|
116
|
-
source = 'captured';
|
|
117
|
-
token = captured;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
107
|
+
const { activation, source, token } = await this.activateWithFallback(id, canonical, grant);
|
|
121
108
|
const record = {
|
|
122
109
|
id,
|
|
123
110
|
folder: canonical,
|
|
@@ -130,20 +117,13 @@ let FolderAccessService = (() => {
|
|
|
130
117
|
lastError: activation.ok ? undefined : (activation.error ?? grantError ?? 'no grant')
|
|
131
118
|
};
|
|
132
119
|
this.upsert(record);
|
|
133
|
-
this.activation[id] = activation.ok
|
|
134
|
-
if (activation.ok)
|
|
120
|
+
this.activation[id] = this.activationStatus(activation.ok, !!token);
|
|
121
|
+
if (activation.ok)
|
|
135
122
|
this.resolvedPaths[id] = activation.resolvedPath ?? canonical;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
this.logEvent({
|
|
143
|
-
message: 'folder grant unverified',
|
|
144
|
-
location, name: this.folderName(canonical), error: record.lastError
|
|
145
|
-
});
|
|
146
|
-
}
|
|
123
|
+
if (activation.ok)
|
|
124
|
+
this.logGrantActivated(source, location, canonical);
|
|
125
|
+
else
|
|
126
|
+
this.logGrantUnverified(location, canonical, record.lastError);
|
|
147
127
|
await this.save(record);
|
|
148
128
|
this.rebuildCoverage();
|
|
149
129
|
this.emitAccessChanged(canonical);
|
|
@@ -152,6 +132,40 @@ let FolderAccessService = (() => {
|
|
|
152
132
|
this.logError(err, 'registerGrant');
|
|
153
133
|
}
|
|
154
134
|
}
|
|
135
|
+
async activateWithFallback(id, canonical, grant) {
|
|
136
|
+
let activation = await this.tryActivate(id, grant);
|
|
137
|
+
let source = 'pick';
|
|
138
|
+
let token = grant;
|
|
139
|
+
if (!activation.ok) {
|
|
140
|
+
const captured = await this.tryCapture(canonical);
|
|
141
|
+
if (captured) {
|
|
142
|
+
const capturedActivation = await this.tryActivate(id, captured);
|
|
143
|
+
if (capturedActivation.ok) {
|
|
144
|
+
activation = capturedActivation;
|
|
145
|
+
source = 'captured';
|
|
146
|
+
token = captured;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { activation, source, token };
|
|
151
|
+
}
|
|
152
|
+
activationStatus(ok, hasToken) {
|
|
153
|
+
if (ok)
|
|
154
|
+
return 'active';
|
|
155
|
+
return hasToken ? 'failed' : 'no-grant';
|
|
156
|
+
}
|
|
157
|
+
logGrantActivated(source, location, canonical) {
|
|
158
|
+
this.logEvent({
|
|
159
|
+
message: source === 'captured' ? 'folder grant captured' : 'folder grant stored',
|
|
160
|
+
location, name: this.folderName(canonical)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
logGrantUnverified(location, canonical, lastError) {
|
|
164
|
+
this.logEvent({
|
|
165
|
+
message: 'folder grant unverified',
|
|
166
|
+
location, name: this.folderName(canonical), error: lastError
|
|
167
|
+
});
|
|
168
|
+
}
|
|
155
169
|
isCovered(path) {
|
|
156
170
|
if (!this.isSupported())
|
|
157
171
|
return false;
|
|
@@ -434,7 +448,7 @@ let FolderAccessService = (() => {
|
|
|
434
448
|
folderName(folder) {
|
|
435
449
|
if (!folder)
|
|
436
450
|
return undefined;
|
|
437
|
-
return folder.split('/').
|
|
451
|
+
return folder.split('/').findLast(s => s.length > 0);
|
|
438
452
|
}
|
|
439
453
|
logAccessState(path, location, state, transient, errno) {
|
|
440
454
|
this.logEvent({ message: 'access state', location, name: this.folderName(path), state, transient, errno });
|
|
@@ -36,8 +36,12 @@ const canonicalPath = (url) => {
|
|
|
36
36
|
if (/^\/[A-Za-z]:/.exec(path))
|
|
37
37
|
path = path.substring(1);
|
|
38
38
|
path = path.replace(/\/{2,}/g, SEPARATOR);
|
|
39
|
-
if (path.length > 1)
|
|
40
|
-
|
|
39
|
+
if (path.length > 1) {
|
|
40
|
+
let end = path.length;
|
|
41
|
+
while (end > 1 && path[end - 1] === SEPARATOR)
|
|
42
|
+
end--;
|
|
43
|
+
path = path.slice(0, end);
|
|
44
|
+
}
|
|
41
45
|
if (!path)
|
|
42
46
|
return undefined;
|
|
43
47
|
if (path === '/private/var')
|
package/lib/cjs/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __exportStar(require("./avatars"), exports);
|
|
|
24
24
|
__exportStar(require("./base/types"), exports);
|
|
25
25
|
__exportStar(require("./coaches"), exports);
|
|
26
26
|
__exportStar(require("./devices"), exports);
|
|
27
|
+
__exportStar(require("./fileaccess"), exports);
|
|
27
28
|
__exportStar(require("./i18n"), exports);
|
|
28
29
|
__exportStar(require("./maps"), exports);
|
|
29
30
|
__exportStar(require("./monitoring"), exports);
|