incyclist-services 1.7.138 → 1.7.139
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/routes/base/model/route.js +4 -0
- package/lib/cjs/routes/library/service.js +53 -9
- package/lib/cjs/routes/list/service.js +12 -3
- package/lib/cjs/workouts/base/parsers/intervals/parser.js +0 -1
- package/lib/cjs/workouts/calendar/sync/intervals/provider.js +8 -1
- package/lib/esm/routes/base/model/route.js +4 -0
- package/lib/esm/routes/library/service.js +53 -9
- package/lib/esm/routes/list/service.js +12 -3
- package/lib/esm/workouts/base/parsers/intervals/parser.js +0 -1
- package/lib/esm/workouts/calendar/sync/intervals/provider.js +8 -1
- package/lib/types/apps/base/api/intervals/types.d.ts +1 -1
- package/lib/types/routes/base/model/route.d.ts +2 -1
- package/lib/types/routes/list/service.d.ts +2 -2
- package/package.json +1 -1
|
@@ -66,6 +66,10 @@ let Route = (() => {
|
|
|
66
66
|
const points = this._details?.points ?? this._description?.points;
|
|
67
67
|
return points;
|
|
68
68
|
}
|
|
69
|
+
replace(update) {
|
|
70
|
+
this._description = { ...update._description };
|
|
71
|
+
this._details = { ...update._details };
|
|
72
|
+
}
|
|
69
73
|
addDetails(details) {
|
|
70
74
|
this._details = details;
|
|
71
75
|
}
|
|
@@ -123,6 +123,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
123
123
|
scan(folderInfo) {
|
|
124
124
|
if (!this.importProps)
|
|
125
125
|
this.prepare();
|
|
126
|
+
this.logEvent({ message: 'import route library', folder: folderInfo.displayName, uri: folderInfo.uri });
|
|
126
127
|
this.isCancelled = false;
|
|
127
128
|
this.scanResult = [];
|
|
128
129
|
this.importProps.phase = 'scanning';
|
|
@@ -137,6 +138,8 @@ let RouteLibraryScannerService = (() => {
|
|
|
137
138
|
this.importProps.scanProgress = progress;
|
|
138
139
|
})
|
|
139
140
|
.on('scan-complete', () => {
|
|
141
|
+
const cntScanned = this.importProps.scanProgress.scannedFolders;
|
|
142
|
+
this.logEvent({ message: 'import route library: scan success', folder: folderInfo.displayName, cntScanned });
|
|
140
143
|
this.importProps.phase = 'parsing';
|
|
141
144
|
});
|
|
142
145
|
return observer;
|
|
@@ -145,6 +148,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
145
148
|
const observer = new types_1.Observer();
|
|
146
149
|
if (!this.importProps)
|
|
147
150
|
this.prepare();
|
|
151
|
+
this.logEvent({ message: 'import route library: parse start', folder: scannedRoutes?.[0].folderName });
|
|
148
152
|
this._parse(scannedRoutes, observer).catch(err => {
|
|
149
153
|
this.logError(err, 'parse');
|
|
150
154
|
observer.emit('error', err.message);
|
|
@@ -165,6 +169,13 @@ let RouteLibraryScannerService = (() => {
|
|
|
165
169
|
}
|
|
166
170
|
});
|
|
167
171
|
observer.on('parse-complete', () => {
|
|
172
|
+
const parseSumamry = {
|
|
173
|
+
cntTotal: this.importProps.routes.length,
|
|
174
|
+
cntSuccess: this.importProps.routes.filter(r => !r.errorReason).length,
|
|
175
|
+
cntError: this.importProps.routes.filter(r => r.errorReason != null).length,
|
|
176
|
+
cntExisting: this.importProps.routes.filter(r => r.alreadyImported).length,
|
|
177
|
+
};
|
|
178
|
+
this.logEvent({ message: 'import route library: parse completed', folder: scannedRoutes?.[0].folderName, parseSumamry });
|
|
168
179
|
this.importProps.phase = 'selecting';
|
|
169
180
|
});
|
|
170
181
|
return observer;
|
|
@@ -215,23 +226,36 @@ let RouteLibraryScannerService = (() => {
|
|
|
215
226
|
}
|
|
216
227
|
}
|
|
217
228
|
async importSingleGpxRoute(fileInfo, observer) {
|
|
218
|
-
|
|
219
|
-
|
|
229
|
+
const name = fileInfo.url ?? fileInfo.filename ?? fileInfo.name;
|
|
230
|
+
this.logEvent({ message: 'import single route file', file: name, type: fileInfo.ext });
|
|
231
|
+
const service = this.getRouteList();
|
|
220
232
|
const db = this.getRoutesDBLoader();
|
|
221
233
|
try {
|
|
222
234
|
const { data, details } = await parsers_1.RouteParser.parse(fileInfo);
|
|
223
235
|
const route = new route_1.Route(data, details);
|
|
236
|
+
this.logEvent({ message: 'import single route file success', file: name });
|
|
237
|
+
const existing = service.findCard(route);
|
|
238
|
+
if (existing) {
|
|
239
|
+
this.logEvent({ message: 'route updated (import)', route: route.title });
|
|
240
|
+
}
|
|
224
241
|
route.description.tsImported = Date.now();
|
|
225
242
|
await db.save(route, true);
|
|
226
|
-
|
|
243
|
+
service.addRoute(route, existing ? 'import' : 'user');
|
|
244
|
+
const cardInfo = service.findCard(route);
|
|
245
|
+
cardInfo?.card?.verify();
|
|
246
|
+
if (existing) {
|
|
247
|
+
existing.card.emitUpdate();
|
|
248
|
+
}
|
|
227
249
|
observer.emit('success', route);
|
|
228
250
|
}
|
|
229
251
|
catch (err) {
|
|
252
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: err.message, stack: err.stack });
|
|
230
253
|
observer.emit('error', err.message);
|
|
231
254
|
}
|
|
232
255
|
}
|
|
233
256
|
async importSingleVideoRoute(fileInfo, observer) {
|
|
234
|
-
|
|
257
|
+
const name = fileInfo.url ?? fileInfo.filename ?? fileInfo.name;
|
|
258
|
+
this.logEvent({ message: 'import single route file', file: name, type: fileInfo.ext });
|
|
235
259
|
const parsers = this.getParsers();
|
|
236
260
|
const { dir, delimiter } = fileInfo;
|
|
237
261
|
let { ext } = fileInfo;
|
|
@@ -240,6 +264,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
240
264
|
const folderUri = dir.endsWith(delimiter ?? '/') ? dir.slice(0, -delimiter.length) : dir;
|
|
241
265
|
try {
|
|
242
266
|
if (!parsers.isPrimaryExtension(ext)) {
|
|
267
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: 'not a route control file' });
|
|
243
268
|
observer.emit('error', 'not a route control file', ext);
|
|
244
269
|
return;
|
|
245
270
|
}
|
|
@@ -249,9 +274,11 @@ let RouteLibraryScannerService = (() => {
|
|
|
249
274
|
this.scanResult = [];
|
|
250
275
|
scanObserver.stop();
|
|
251
276
|
if (!files.length) {
|
|
277
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: 'no file found' });
|
|
252
278
|
observer.emit('error', 'no file found');
|
|
253
279
|
}
|
|
254
280
|
if (files[0].scanError) {
|
|
281
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: files[0].scanError });
|
|
255
282
|
observer.emit('error', files[0].scanError);
|
|
256
283
|
return;
|
|
257
284
|
}
|
|
@@ -261,6 +288,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
261
288
|
parseObserver.on('parse-result', (result) => {
|
|
262
289
|
parseObserver.stop();
|
|
263
290
|
if (result.parseError) {
|
|
291
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: result.parseError });
|
|
264
292
|
observer.emit('error', result.parseError);
|
|
265
293
|
return;
|
|
266
294
|
}
|
|
@@ -268,15 +296,18 @@ let RouteLibraryScannerService = (() => {
|
|
|
268
296
|
let ingestError;
|
|
269
297
|
ingest.once('ingest-error', (_, reason) => {
|
|
270
298
|
ingest.stop();
|
|
299
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason });
|
|
271
300
|
observer.emit('error', reason);
|
|
272
301
|
ingestError = reason;
|
|
273
302
|
});
|
|
274
303
|
ingest.once('ingest-complete', (summary) => {
|
|
275
304
|
ingest.stop();
|
|
276
305
|
if (!ingestError && summary.imported > 0) {
|
|
306
|
+
this.logEvent({ message: 'import single route file success', file: name });
|
|
277
307
|
observer.emit('success', summary.importedRoutes?.[0]?.title);
|
|
278
308
|
}
|
|
279
309
|
else if (!ingestError && summary.imported === 0) {
|
|
310
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: 'not imported' });
|
|
280
311
|
observer.emit('error', 'not imported');
|
|
281
312
|
}
|
|
282
313
|
});
|
|
@@ -284,6 +315,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
284
315
|
}
|
|
285
316
|
}
|
|
286
317
|
catch (err) {
|
|
318
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: err.message, stack: err.stack });
|
|
287
319
|
observer.emit('error', err.message);
|
|
288
320
|
}
|
|
289
321
|
}
|
|
@@ -299,7 +331,6 @@ let RouteLibraryScannerService = (() => {
|
|
|
299
331
|
}
|
|
300
332
|
async scanFolder(uri, folderName, observer, parsers, progress, discoveredCount, recursive = true) {
|
|
301
333
|
const fs = this.getBindings().fs;
|
|
302
|
-
this.logEvent({ message: 'scanFolder', uri, folderName });
|
|
303
334
|
let entries;
|
|
304
335
|
try {
|
|
305
336
|
entries = await fs.readdir(uri, { recursive: false, extended: true });
|
|
@@ -393,7 +424,6 @@ let RouteLibraryScannerService = (() => {
|
|
|
393
424
|
fileName = info.base;
|
|
394
425
|
}
|
|
395
426
|
catch { }
|
|
396
|
-
this.logEvent({ message: 'parsing route', fileName });
|
|
397
427
|
await this._parseTarget(target, service, observer);
|
|
398
428
|
this.logEvent({ message: 'parsing route done', fileName });
|
|
399
429
|
}
|
|
@@ -446,7 +476,6 @@ let RouteLibraryScannerService = (() => {
|
|
|
446
476
|
validateVideoUrl(route, folderUri, folderFiles) {
|
|
447
477
|
const { video } = route?.details ?? {};
|
|
448
478
|
const { file, url, format } = video ?? {};
|
|
449
|
-
this.logEvent({ message: 'validateVideoUrl', route: { file, url, format }, folderUri, folderFiles });
|
|
450
479
|
let videoFormat = format;
|
|
451
480
|
try {
|
|
452
481
|
if (videoFormat === undefined) {
|
|
@@ -511,7 +540,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
511
540
|
await (0, utils_1.waitNextTick)();
|
|
512
541
|
const service = this.getRouteList();
|
|
513
542
|
const db = this.getRoutesDBLoader();
|
|
514
|
-
const target = routes.filter(r => !r.
|
|
543
|
+
const target = routes.filter(r => !r.parseError);
|
|
515
544
|
const total = target.length;
|
|
516
545
|
let errors = 0;
|
|
517
546
|
const failedRoutes = [];
|
|
@@ -520,10 +549,25 @@ let RouteLibraryScannerService = (() => {
|
|
|
520
549
|
if (this.isCancelled)
|
|
521
550
|
continue;
|
|
522
551
|
const { route } = target[i] ?? {};
|
|
552
|
+
const isExisting = target[i].alreadyImported;
|
|
553
|
+
const existing = isExisting ? service.findCard(route) : null;
|
|
523
554
|
try {
|
|
524
555
|
observer.emit('ingest-progress', { current: i + 1, total, currentName: route.title });
|
|
556
|
+
if (existing) {
|
|
557
|
+
this.logEvent({ message: 'route updated (library import)', route: route.title });
|
|
558
|
+
}
|
|
559
|
+
route.description.tsImported = Date.now();
|
|
525
560
|
await db.save(route, true);
|
|
526
|
-
service.addRoute(route, 'user');
|
|
561
|
+
service.addRoute(route, existing ? 'import' : 'user');
|
|
562
|
+
try {
|
|
563
|
+
const cardInfo = service.findCard(route);
|
|
564
|
+
cardInfo?.card?.verify();
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
}
|
|
568
|
+
if (existing) {
|
|
569
|
+
existing.card.emitUpdate();
|
|
570
|
+
}
|
|
527
571
|
importedRoutes.push(route);
|
|
528
572
|
}
|
|
529
573
|
catch (err) {
|
|
@@ -760,6 +760,7 @@ let RouteListService = (() => {
|
|
|
760
760
|
return;
|
|
761
761
|
const importCard = importId ? null : importCards[idx];
|
|
762
762
|
const name = file.url ?? file.filename ?? file.name;
|
|
763
|
+
this.logEvent({ message: 'import single route file', file: name, type: file.ext });
|
|
763
764
|
try {
|
|
764
765
|
if (importId && observer)
|
|
765
766
|
observer?.emit('parsing', importId);
|
|
@@ -768,12 +769,15 @@ let RouteListService = (() => {
|
|
|
768
769
|
observer?.emit('success', importId);
|
|
769
770
|
const route = new route_1.Route(data, details);
|
|
770
771
|
route.description.tsImported = Date.now();
|
|
772
|
+
this.logEvent({ message: 'import single route file success', file: name });
|
|
771
773
|
const existing = this.findCard(route);
|
|
772
774
|
if (existing) {
|
|
773
775
|
existing.list.remove(existing.card);
|
|
776
|
+
this.logEvent({ message: 'route updated (import)', route: route.title });
|
|
774
777
|
}
|
|
775
778
|
else {
|
|
776
779
|
this.routes.push(route);
|
|
780
|
+
this.logEvent({ message: 'route added', route: route.title });
|
|
777
781
|
}
|
|
778
782
|
const card = new RouteCard_1.RouteCard(route, { list: this.myRoutes });
|
|
779
783
|
card.verify();
|
|
@@ -789,7 +793,7 @@ let RouteListService = (() => {
|
|
|
789
793
|
this.verifyPoints(card, route);
|
|
790
794
|
}
|
|
791
795
|
catch (err) {
|
|
792
|
-
this.logEvent({ message: 'import failed', name, reason: err.message, stack: err.stack });
|
|
796
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: err.message, stack: err.stack });
|
|
793
797
|
if (importId && observer)
|
|
794
798
|
observer?.emit('error', importId, err.message);
|
|
795
799
|
if (importCard)
|
|
@@ -911,7 +915,10 @@ let RouteListService = (() => {
|
|
|
911
915
|
}
|
|
912
916
|
}
|
|
913
917
|
addRoute(route, source = 'system') {
|
|
914
|
-
|
|
918
|
+
const existing = source === 'system' ? this.getRoute(route.description.id) :
|
|
919
|
+
this.routes.find(r => r.description?.id === route.description.id);
|
|
920
|
+
if (existing) {
|
|
921
|
+
existing.replace(route);
|
|
915
922
|
return;
|
|
916
923
|
}
|
|
917
924
|
this.routes.push(route);
|
|
@@ -929,6 +936,8 @@ let RouteListService = (() => {
|
|
|
929
936
|
if (updated) {
|
|
930
937
|
this.logEvent({ message: 'route updated (country)', route: route.title, country: route.description?.country });
|
|
931
938
|
this.db.save(route, false);
|
|
939
|
+
const cardInfo = this.findCard(route);
|
|
940
|
+
cardInfo?.card?.emitUpdate();
|
|
932
941
|
}
|
|
933
942
|
});
|
|
934
943
|
}
|
|
@@ -940,7 +949,7 @@ let RouteListService = (() => {
|
|
|
940
949
|
list.add(card);
|
|
941
950
|
if (list.getId() === 'myRoutes')
|
|
942
951
|
card.enableDelete(true);
|
|
943
|
-
this.emitLists('updated', { source });
|
|
952
|
+
this.emitLists('updated', { source: source === 'user' ? 'user' : 'system' });
|
|
944
953
|
}
|
|
945
954
|
async verifyPoints(card, route) {
|
|
946
955
|
const updated = await route.updateCountryFromPoints();
|
|
@@ -120,7 +120,11 @@ let IntervalsCalendarSyncProvider = (() => {
|
|
|
120
120
|
}
|
|
121
121
|
const oldest = (0, utils_1.getFirstDayOfCurrentWeek)();
|
|
122
122
|
const cw = await this.getIntervalsApi().getCalendarWorkouts({ oldest, days: 30, ext: 'zwo' }) ?? [];
|
|
123
|
-
const cycling = cw.filter(w => w.type === 'Ride');
|
|
123
|
+
const cycling = cw.filter(w => w.type === 'Ride' || w.type === 'VirtualRide');
|
|
124
|
+
this.logger.logEvent({ message: 'workout sync', externalApp: 'intervals',
|
|
125
|
+
oldest: oldest.toLocaleDateString(), cntTotal: cw.length, cntCycling: cycling.length,
|
|
126
|
+
workouts: cw.map(cw => `${cw.type}:${cw.name}`).join('|')
|
|
127
|
+
});
|
|
124
128
|
await this.parseWorkouts(cycling);
|
|
125
129
|
this.observer.emit(event, this.workouts, 'intervals');
|
|
126
130
|
this.lastSyncTS = Date.now();
|
|
@@ -166,9 +170,11 @@ let IntervalsCalendarSyncProvider = (() => {
|
|
|
166
170
|
if (this.lastSyncTS > 0) {
|
|
167
171
|
if (!isExisting) {
|
|
168
172
|
this.observer.emit('added', w, 'intervals');
|
|
173
|
+
this.logEvent({ message: 'workout added', externalApp: 'intervals', workout: w.workout?.name });
|
|
169
174
|
}
|
|
170
175
|
else if (updated && updated.valueOf() > this.lastSyncTS) {
|
|
171
176
|
this.observer.emit('updated', w, 'intervals');
|
|
177
|
+
this.logEvent({ message: 'workout updated', externalApp: 'intervals', workout: w.workout?.name });
|
|
172
178
|
}
|
|
173
179
|
}
|
|
174
180
|
if (isExisting) {
|
|
@@ -186,6 +192,7 @@ let IntervalsCalendarSyncProvider = (() => {
|
|
|
186
192
|
const deletedWorkouts = this.workouts.filter(w => !events.some(e => e.id.toString() === w.workoutId));
|
|
187
193
|
for (const w of deletedWorkouts) {
|
|
188
194
|
this.observer.emit('deleted', w, 'intervals');
|
|
195
|
+
this.logEvent({ message: 'workout deleted', externalApp: 'intervals', workout: w.workout?.name });
|
|
189
196
|
}
|
|
190
197
|
this.workouts = this.workouts.filter(w => events.find(e => e.id.toString() === w.workoutId));
|
|
191
198
|
}
|
|
@@ -60,6 +60,10 @@ let Route = (() => {
|
|
|
60
60
|
const points = this._details?.points ?? this._description?.points;
|
|
61
61
|
return points;
|
|
62
62
|
}
|
|
63
|
+
replace(update) {
|
|
64
|
+
this._description = { ...update._description };
|
|
65
|
+
this._details = { ...update._details };
|
|
66
|
+
}
|
|
63
67
|
addDetails(details) {
|
|
64
68
|
this._details = details;
|
|
65
69
|
}
|
|
@@ -120,6 +120,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
120
120
|
scan(folderInfo) {
|
|
121
121
|
if (!this.importProps)
|
|
122
122
|
this.prepare();
|
|
123
|
+
this.logEvent({ message: 'import route library', folder: folderInfo.displayName, uri: folderInfo.uri });
|
|
123
124
|
this.isCancelled = false;
|
|
124
125
|
this.scanResult = [];
|
|
125
126
|
this.importProps.phase = 'scanning';
|
|
@@ -134,6 +135,8 @@ let RouteLibraryScannerService = (() => {
|
|
|
134
135
|
this.importProps.scanProgress = progress;
|
|
135
136
|
})
|
|
136
137
|
.on('scan-complete', () => {
|
|
138
|
+
const cntScanned = this.importProps.scanProgress.scannedFolders;
|
|
139
|
+
this.logEvent({ message: 'import route library: scan success', folder: folderInfo.displayName, cntScanned });
|
|
137
140
|
this.importProps.phase = 'parsing';
|
|
138
141
|
});
|
|
139
142
|
return observer;
|
|
@@ -142,6 +145,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
142
145
|
const observer = new Observer();
|
|
143
146
|
if (!this.importProps)
|
|
144
147
|
this.prepare();
|
|
148
|
+
this.logEvent({ message: 'import route library: parse start', folder: scannedRoutes?.[0].folderName });
|
|
145
149
|
this._parse(scannedRoutes, observer).catch(err => {
|
|
146
150
|
this.logError(err, 'parse');
|
|
147
151
|
observer.emit('error', err.message);
|
|
@@ -162,6 +166,13 @@ let RouteLibraryScannerService = (() => {
|
|
|
162
166
|
}
|
|
163
167
|
});
|
|
164
168
|
observer.on('parse-complete', () => {
|
|
169
|
+
const parseSumamry = {
|
|
170
|
+
cntTotal: this.importProps.routes.length,
|
|
171
|
+
cntSuccess: this.importProps.routes.filter(r => !r.errorReason).length,
|
|
172
|
+
cntError: this.importProps.routes.filter(r => r.errorReason != null).length,
|
|
173
|
+
cntExisting: this.importProps.routes.filter(r => r.alreadyImported).length,
|
|
174
|
+
};
|
|
175
|
+
this.logEvent({ message: 'import route library: parse completed', folder: scannedRoutes?.[0].folderName, parseSumamry });
|
|
165
176
|
this.importProps.phase = 'selecting';
|
|
166
177
|
});
|
|
167
178
|
return observer;
|
|
@@ -212,23 +223,36 @@ let RouteLibraryScannerService = (() => {
|
|
|
212
223
|
}
|
|
213
224
|
}
|
|
214
225
|
async importSingleGpxRoute(fileInfo, observer) {
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
const name = fileInfo.url ?? fileInfo.filename ?? fileInfo.name;
|
|
227
|
+
this.logEvent({ message: 'import single route file', file: name, type: fileInfo.ext });
|
|
228
|
+
const service = this.getRouteList();
|
|
217
229
|
const db = this.getRoutesDBLoader();
|
|
218
230
|
try {
|
|
219
231
|
const { data, details } = await RouteParser.parse(fileInfo);
|
|
220
232
|
const route = new Route(data, details);
|
|
233
|
+
this.logEvent({ message: 'import single route file success', file: name });
|
|
234
|
+
const existing = service.findCard(route);
|
|
235
|
+
if (existing) {
|
|
236
|
+
this.logEvent({ message: 'route updated (import)', route: route.title });
|
|
237
|
+
}
|
|
221
238
|
route.description.tsImported = Date.now();
|
|
222
239
|
await db.save(route, true);
|
|
223
|
-
|
|
240
|
+
service.addRoute(route, existing ? 'import' : 'user');
|
|
241
|
+
const cardInfo = service.findCard(route);
|
|
242
|
+
cardInfo?.card?.verify();
|
|
243
|
+
if (existing) {
|
|
244
|
+
existing.card.emitUpdate();
|
|
245
|
+
}
|
|
224
246
|
observer.emit('success', route);
|
|
225
247
|
}
|
|
226
248
|
catch (err) {
|
|
249
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: err.message, stack: err.stack });
|
|
227
250
|
observer.emit('error', err.message);
|
|
228
251
|
}
|
|
229
252
|
}
|
|
230
253
|
async importSingleVideoRoute(fileInfo, observer) {
|
|
231
|
-
|
|
254
|
+
const name = fileInfo.url ?? fileInfo.filename ?? fileInfo.name;
|
|
255
|
+
this.logEvent({ message: 'import single route file', file: name, type: fileInfo.ext });
|
|
232
256
|
const parsers = this.getParsers();
|
|
233
257
|
const { dir, delimiter } = fileInfo;
|
|
234
258
|
let { ext } = fileInfo;
|
|
@@ -237,6 +261,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
237
261
|
const folderUri = dir.endsWith(delimiter ?? '/') ? dir.slice(0, -delimiter.length) : dir;
|
|
238
262
|
try {
|
|
239
263
|
if (!parsers.isPrimaryExtension(ext)) {
|
|
264
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: 'not a route control file' });
|
|
240
265
|
observer.emit('error', 'not a route control file', ext);
|
|
241
266
|
return;
|
|
242
267
|
}
|
|
@@ -246,9 +271,11 @@ let RouteLibraryScannerService = (() => {
|
|
|
246
271
|
this.scanResult = [];
|
|
247
272
|
scanObserver.stop();
|
|
248
273
|
if (!files.length) {
|
|
274
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: 'no file found' });
|
|
249
275
|
observer.emit('error', 'no file found');
|
|
250
276
|
}
|
|
251
277
|
if (files[0].scanError) {
|
|
278
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: files[0].scanError });
|
|
252
279
|
observer.emit('error', files[0].scanError);
|
|
253
280
|
return;
|
|
254
281
|
}
|
|
@@ -258,6 +285,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
258
285
|
parseObserver.on('parse-result', (result) => {
|
|
259
286
|
parseObserver.stop();
|
|
260
287
|
if (result.parseError) {
|
|
288
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: result.parseError });
|
|
261
289
|
observer.emit('error', result.parseError);
|
|
262
290
|
return;
|
|
263
291
|
}
|
|
@@ -265,15 +293,18 @@ let RouteLibraryScannerService = (() => {
|
|
|
265
293
|
let ingestError;
|
|
266
294
|
ingest.once('ingest-error', (_, reason) => {
|
|
267
295
|
ingest.stop();
|
|
296
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason });
|
|
268
297
|
observer.emit('error', reason);
|
|
269
298
|
ingestError = reason;
|
|
270
299
|
});
|
|
271
300
|
ingest.once('ingest-complete', (summary) => {
|
|
272
301
|
ingest.stop();
|
|
273
302
|
if (!ingestError && summary.imported > 0) {
|
|
303
|
+
this.logEvent({ message: 'import single route file success', file: name });
|
|
274
304
|
observer.emit('success', summary.importedRoutes?.[0]?.title);
|
|
275
305
|
}
|
|
276
306
|
else if (!ingestError && summary.imported === 0) {
|
|
307
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: 'not imported' });
|
|
277
308
|
observer.emit('error', 'not imported');
|
|
278
309
|
}
|
|
279
310
|
});
|
|
@@ -281,6 +312,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
281
312
|
}
|
|
282
313
|
}
|
|
283
314
|
catch (err) {
|
|
315
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: err.message, stack: err.stack });
|
|
284
316
|
observer.emit('error', err.message);
|
|
285
317
|
}
|
|
286
318
|
}
|
|
@@ -296,7 +328,6 @@ let RouteLibraryScannerService = (() => {
|
|
|
296
328
|
}
|
|
297
329
|
async scanFolder(uri, folderName, observer, parsers, progress, discoveredCount, recursive = true) {
|
|
298
330
|
const fs = this.getBindings().fs;
|
|
299
|
-
this.logEvent({ message: 'scanFolder', uri, folderName });
|
|
300
331
|
let entries;
|
|
301
332
|
try {
|
|
302
333
|
entries = await fs.readdir(uri, { recursive: false, extended: true });
|
|
@@ -390,7 +421,6 @@ let RouteLibraryScannerService = (() => {
|
|
|
390
421
|
fileName = info.base;
|
|
391
422
|
}
|
|
392
423
|
catch { }
|
|
393
|
-
this.logEvent({ message: 'parsing route', fileName });
|
|
394
424
|
await this._parseTarget(target, service, observer);
|
|
395
425
|
this.logEvent({ message: 'parsing route done', fileName });
|
|
396
426
|
}
|
|
@@ -443,7 +473,6 @@ let RouteLibraryScannerService = (() => {
|
|
|
443
473
|
validateVideoUrl(route, folderUri, folderFiles) {
|
|
444
474
|
const { video } = route?.details ?? {};
|
|
445
475
|
const { file, url, format } = video ?? {};
|
|
446
|
-
this.logEvent({ message: 'validateVideoUrl', route: { file, url, format }, folderUri, folderFiles });
|
|
447
476
|
let videoFormat = format;
|
|
448
477
|
try {
|
|
449
478
|
if (videoFormat === undefined) {
|
|
@@ -508,7 +537,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
508
537
|
await waitNextTick();
|
|
509
538
|
const service = this.getRouteList();
|
|
510
539
|
const db = this.getRoutesDBLoader();
|
|
511
|
-
const target = routes.filter(r => !r.
|
|
540
|
+
const target = routes.filter(r => !r.parseError);
|
|
512
541
|
const total = target.length;
|
|
513
542
|
let errors = 0;
|
|
514
543
|
const failedRoutes = [];
|
|
@@ -517,10 +546,25 @@ let RouteLibraryScannerService = (() => {
|
|
|
517
546
|
if (this.isCancelled)
|
|
518
547
|
continue;
|
|
519
548
|
const { route } = target[i] ?? {};
|
|
549
|
+
const isExisting = target[i].alreadyImported;
|
|
550
|
+
const existing = isExisting ? service.findCard(route) : null;
|
|
520
551
|
try {
|
|
521
552
|
observer.emit('ingest-progress', { current: i + 1, total, currentName: route.title });
|
|
553
|
+
if (existing) {
|
|
554
|
+
this.logEvent({ message: 'route updated (library import)', route: route.title });
|
|
555
|
+
}
|
|
556
|
+
route.description.tsImported = Date.now();
|
|
522
557
|
await db.save(route, true);
|
|
523
|
-
service.addRoute(route, 'user');
|
|
558
|
+
service.addRoute(route, existing ? 'import' : 'user');
|
|
559
|
+
try {
|
|
560
|
+
const cardInfo = service.findCard(route);
|
|
561
|
+
cardInfo?.card?.verify();
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
}
|
|
565
|
+
if (existing) {
|
|
566
|
+
existing.card.emitUpdate();
|
|
567
|
+
}
|
|
524
568
|
importedRoutes.push(route);
|
|
525
569
|
}
|
|
526
570
|
catch (err) {
|
|
@@ -754,6 +754,7 @@ let RouteListService = (() => {
|
|
|
754
754
|
return;
|
|
755
755
|
const importCard = importId ? null : importCards[idx];
|
|
756
756
|
const name = file.url ?? file.filename ?? file.name;
|
|
757
|
+
this.logEvent({ message: 'import single route file', file: name, type: file.ext });
|
|
757
758
|
try {
|
|
758
759
|
if (importId && observer)
|
|
759
760
|
observer?.emit('parsing', importId);
|
|
@@ -762,12 +763,15 @@ let RouteListService = (() => {
|
|
|
762
763
|
observer?.emit('success', importId);
|
|
763
764
|
const route = new Route(data, details);
|
|
764
765
|
route.description.tsImported = Date.now();
|
|
766
|
+
this.logEvent({ message: 'import single route file success', file: name });
|
|
765
767
|
const existing = this.findCard(route);
|
|
766
768
|
if (existing) {
|
|
767
769
|
existing.list.remove(existing.card);
|
|
770
|
+
this.logEvent({ message: 'route updated (import)', route: route.title });
|
|
768
771
|
}
|
|
769
772
|
else {
|
|
770
773
|
this.routes.push(route);
|
|
774
|
+
this.logEvent({ message: 'route added', route: route.title });
|
|
771
775
|
}
|
|
772
776
|
const card = new RouteCard(route, { list: this.myRoutes });
|
|
773
777
|
card.verify();
|
|
@@ -783,7 +787,7 @@ let RouteListService = (() => {
|
|
|
783
787
|
this.verifyPoints(card, route);
|
|
784
788
|
}
|
|
785
789
|
catch (err) {
|
|
786
|
-
this.logEvent({ message: 'import failed', name, reason: err.message, stack: err.stack });
|
|
790
|
+
this.logEvent({ message: 'import single route file failed', file: name, reason: err.message, stack: err.stack });
|
|
787
791
|
if (importId && observer)
|
|
788
792
|
observer?.emit('error', importId, err.message);
|
|
789
793
|
if (importCard)
|
|
@@ -905,7 +909,10 @@ let RouteListService = (() => {
|
|
|
905
909
|
}
|
|
906
910
|
}
|
|
907
911
|
addRoute(route, source = 'system') {
|
|
908
|
-
|
|
912
|
+
const existing = source === 'system' ? this.getRoute(route.description.id) :
|
|
913
|
+
this.routes.find(r => r.description?.id === route.description.id);
|
|
914
|
+
if (existing) {
|
|
915
|
+
existing.replace(route);
|
|
909
916
|
return;
|
|
910
917
|
}
|
|
911
918
|
this.routes.push(route);
|
|
@@ -923,6 +930,8 @@ let RouteListService = (() => {
|
|
|
923
930
|
if (updated) {
|
|
924
931
|
this.logEvent({ message: 'route updated (country)', route: route.title, country: route.description?.country });
|
|
925
932
|
this.db.save(route, false);
|
|
933
|
+
const cardInfo = this.findCard(route);
|
|
934
|
+
cardInfo?.card?.emitUpdate();
|
|
926
935
|
}
|
|
927
936
|
});
|
|
928
937
|
}
|
|
@@ -934,7 +943,7 @@ let RouteListService = (() => {
|
|
|
934
943
|
list.add(card);
|
|
935
944
|
if (list.getId() === 'myRoutes')
|
|
936
945
|
card.enableDelete(true);
|
|
937
|
-
this.emitLists('updated', { source });
|
|
946
|
+
this.emitLists('updated', { source: source === 'user' ? 'user' : 'system' });
|
|
938
947
|
}
|
|
939
948
|
async verifyPoints(card, route) {
|
|
940
949
|
const updated = await route.updateCountryFromPoints();
|
|
@@ -117,7 +117,11 @@ let IntervalsCalendarSyncProvider = (() => {
|
|
|
117
117
|
}
|
|
118
118
|
const oldest = getFirstDayOfCurrentWeek();
|
|
119
119
|
const cw = await this.getIntervalsApi().getCalendarWorkouts({ oldest, days: 30, ext: 'zwo' }) ?? [];
|
|
120
|
-
const cycling = cw.filter(w => w.type === 'Ride');
|
|
120
|
+
const cycling = cw.filter(w => w.type === 'Ride' || w.type === 'VirtualRide');
|
|
121
|
+
this.logger.logEvent({ message: 'workout sync', externalApp: 'intervals',
|
|
122
|
+
oldest: oldest.toLocaleDateString(), cntTotal: cw.length, cntCycling: cycling.length,
|
|
123
|
+
workouts: cw.map(cw => `${cw.type}:${cw.name}`).join('|')
|
|
124
|
+
});
|
|
121
125
|
await this.parseWorkouts(cycling);
|
|
122
126
|
this.observer.emit(event, this.workouts, 'intervals');
|
|
123
127
|
this.lastSyncTS = Date.now();
|
|
@@ -163,9 +167,11 @@ let IntervalsCalendarSyncProvider = (() => {
|
|
|
163
167
|
if (this.lastSyncTS > 0) {
|
|
164
168
|
if (!isExisting) {
|
|
165
169
|
this.observer.emit('added', w, 'intervals');
|
|
170
|
+
this.logEvent({ message: 'workout added', externalApp: 'intervals', workout: w.workout?.name });
|
|
166
171
|
}
|
|
167
172
|
else if (updated && updated.valueOf() > this.lastSyncTS) {
|
|
168
173
|
this.observer.emit('updated', w, 'intervals');
|
|
174
|
+
this.logEvent({ message: 'workout updated', externalApp: 'intervals', workout: w.workout?.name });
|
|
169
175
|
}
|
|
170
176
|
}
|
|
171
177
|
if (isExisting) {
|
|
@@ -183,6 +189,7 @@ let IntervalsCalendarSyncProvider = (() => {
|
|
|
183
189
|
const deletedWorkouts = this.workouts.filter(w => !events.some(e => e.id.toString() === w.workoutId));
|
|
184
190
|
for (const w of deletedWorkouts) {
|
|
185
191
|
this.observer.emit('deleted', w, 'intervals');
|
|
192
|
+
this.logEvent({ message: 'workout deleted', externalApp: 'intervals', workout: w.workout?.name });
|
|
186
193
|
}
|
|
187
194
|
this.workouts = this.workouts.filter(w => events.find(e => e.id.toString() === w.workoutId));
|
|
188
195
|
}
|
|
@@ -53,7 +53,7 @@ export type IntervalsCalendarEvent = {
|
|
|
53
53
|
icu_training_load: number;
|
|
54
54
|
start_date_local: string;
|
|
55
55
|
end_date_local: string;
|
|
56
|
-
type: 'Ride' | 'Run' | 'Swim' | 'Walk' | 'Weight Training';
|
|
56
|
+
type: 'Ride' | 'Run' | 'Swim' | 'Walk' | 'Weight Training' | 'VirtualRide' | 'MountainBikeRide' | 'GravelRide';
|
|
57
57
|
calendar_id: number;
|
|
58
58
|
uid: string;
|
|
59
59
|
athlete_id: string;
|
|
@@ -2,11 +2,12 @@ import { Countries } from "../../../i18n/countries";
|
|
|
2
2
|
import { RouteApiDetail } from "../api/types";
|
|
3
3
|
import { RouteInfo, RoutePoint } from "../types";
|
|
4
4
|
export declare class Route {
|
|
5
|
-
private
|
|
5
|
+
private _description;
|
|
6
6
|
private _details;
|
|
7
7
|
constructor(info: RouteInfo, raw?: RouteApiDetail);
|
|
8
8
|
get description(): RouteInfo;
|
|
9
9
|
get points(): Array<RoutePoint>;
|
|
10
|
+
replace(update: Route): void;
|
|
10
11
|
addDetails(details: RouteApiDetail): void;
|
|
11
12
|
get details(): RouteApiDetail;
|
|
12
13
|
set details(details: RouteApiDetail);
|
|
@@ -138,7 +138,7 @@ export declare class RouteListService extends IncyclistService implements IRoute
|
|
|
138
138
|
protected getFilterCountries(): Array<string>;
|
|
139
139
|
existsBySourceUri(uri: string): boolean;
|
|
140
140
|
getBySourceUri(uri: string): Route;
|
|
141
|
-
addRoute(route: Route, source?: 'user' | 'system'): void;
|
|
141
|
+
addRoute(route: Route, source?: 'user' | 'system' | 'import'): void;
|
|
142
142
|
protected verifyPoints(card: RouteCard, route: Route): Promise<void>;
|
|
143
143
|
protected addFromApi(route: Route): Promise<void>;
|
|
144
144
|
protected update(route: Route, source?: 'user' | 'system'): Promise<void>;
|
|
@@ -168,7 +168,7 @@ export declare class RouteListService extends IncyclistService implements IRoute
|
|
|
168
168
|
protected checkExistingPreviewFiles(descr: RouteInfo): Promise<string | undefined>;
|
|
169
169
|
protected doCreatePreview(descr: RouteInfo): Promise<string>;
|
|
170
170
|
protected processPreviewQueue(): PromiseObserver<void>;
|
|
171
|
-
|
|
171
|
+
findCard(target: Route | string): {
|
|
172
172
|
card: RouteCard;
|
|
173
173
|
list: CardList<Route>;
|
|
174
174
|
};
|