incyclist-services 1.7.55 → 1.7.56

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.
Files changed (43) hide show
  1. package/lib/cjs/devices/page/service.js +13 -1
  2. package/lib/cjs/ride/route/RLVDisplayService.js +2 -0
  3. package/lib/cjs/routes/base/model/route.js +1 -4
  4. package/lib/cjs/routes/base/parsers/epm.js +1 -0
  5. package/lib/cjs/routes/base/parsers/geometry.js +3 -4
  6. package/lib/cjs/routes/base/parsers/index.js +10 -2
  7. package/lib/cjs/routes/base/parsers/multixml.js +3 -1
  8. package/lib/cjs/routes/base/parsers/tacx/TacxParser.js +11 -15
  9. package/lib/cjs/routes/base/parsers/utils.js +43 -1
  10. package/lib/cjs/routes/base/parsers/xml.js +4 -1
  11. package/lib/cjs/routes/library/service.js +65 -28
  12. package/lib/cjs/routes/list/service.js +41 -9
  13. package/lib/cjs/routes/page/service.js +5 -1
  14. package/lib/cjs/video/VideoSyncHelper.js +14 -3
  15. package/lib/cjs/workouts/base/parsers/incyclist/Json.js +3 -1
  16. package/lib/cjs/workouts/base/parsers/intervals/parser.js +3 -1
  17. package/lib/cjs/workouts/base/parsers/zwo/zwo.js +3 -1
  18. package/lib/esm/devices/page/service.js +13 -1
  19. package/lib/esm/ride/route/RLVDisplayService.js +2 -0
  20. package/lib/esm/routes/base/model/route.js +1 -4
  21. package/lib/esm/routes/base/parsers/epm.js +1 -0
  22. package/lib/esm/routes/base/parsers/geometry.js +3 -4
  23. package/lib/esm/routes/base/parsers/index.js +10 -2
  24. package/lib/esm/routes/base/parsers/multixml.js +3 -1
  25. package/lib/esm/routes/base/parsers/tacx/TacxParser.js +11 -15
  26. package/lib/esm/routes/base/parsers/utils.js +40 -0
  27. package/lib/esm/routes/base/parsers/xml.js +5 -2
  28. package/lib/esm/routes/library/service.js +65 -28
  29. package/lib/esm/routes/list/service.js +41 -9
  30. package/lib/esm/routes/page/service.js +5 -1
  31. package/lib/esm/video/VideoSyncHelper.js +14 -3
  32. package/lib/esm/workouts/base/parsers/incyclist/Json.js +3 -1
  33. package/lib/esm/workouts/base/parsers/intervals/parser.js +3 -1
  34. package/lib/esm/workouts/base/parsers/zwo/zwo.js +3 -1
  35. package/lib/types/api/fs/index.d.ts +1 -1
  36. package/lib/types/api/repository/types.d.ts +1 -2
  37. package/lib/types/devices/page/service.d.ts +1 -0
  38. package/lib/types/routes/base/parsers/utils.d.ts +2 -0
  39. package/lib/types/routes/library/service.d.ts +1 -0
  40. package/lib/types/routes/library/types.d.ts +1 -1
  41. package/lib/types/routes/list/service.d.ts +4 -0
  42. package/lib/types/routes/page/service.d.ts +1 -0
  43. package/package.json +1 -1
@@ -45,6 +45,7 @@ const access_1 = require("../access");
45
45
  const configuration_1 = require("../configuration");
46
46
  const ui_1 = require("../../ui");
47
47
  const types_1 = require("../../base/types");
48
+ const ride_1 = require("../ride");
48
49
  let DevicesPageService = (() => {
49
50
  let _classDecorators = [decorators_1.Singleton];
50
51
  let _classDescriptor;
@@ -53,6 +54,7 @@ let DevicesPageService = (() => {
53
54
  let _classSuper = pages_1.IncyclistPageService;
54
55
  let _instanceExtraInitializers = [];
55
56
  let _getDevicePairing_decorators;
57
+ let _getDeviceRide_decorators;
56
58
  let _getDeviceConfiguration_decorators;
57
59
  let _getIncyclist_decorators;
58
60
  var DevicesPageService = class extends _classSuper {
@@ -60,9 +62,11 @@ let DevicesPageService = (() => {
60
62
  static {
61
63
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
62
64
  _getDevicePairing_decorators = [decorators_1.Injectable];
65
+ _getDeviceRide_decorators = [decorators_1.Injectable];
63
66
  _getDeviceConfiguration_decorators = [decorators_1.Injectable];
64
67
  _getIncyclist_decorators = [decorators_1.Injectable];
65
68
  __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);
69
+ __esDecorate(this, null, _getDeviceRide_decorators, { kind: "method", name: "getDeviceRide", static: false, private: false, access: { has: obj => "getDeviceRide" in obj, get: obj => obj.getDeviceRide }, metadata: _metadata }, null, _instanceExtraInitializers);
66
70
  __esDecorate(this, null, _getDeviceConfiguration_decorators, { kind: "method", name: "getDeviceConfiguration", static: false, private: false, access: { has: obj => "getDeviceConfiguration" in obj, get: obj => obj.getDeviceConfiguration }, metadata: _metadata }, null, _instanceExtraInitializers);
67
71
  __esDecorate(this, null, _getIncyclist_decorators, { kind: "method", name: "getIncyclist", static: false, private: false, access: { has: obj => "getIncyclist" in obj, get: obj => obj.getIncyclist }, metadata: _metadata }, null, _instanceExtraInitializers);
68
72
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
@@ -325,6 +329,12 @@ let DevicesPageService = (() => {
325
329
  return [
326
330
  { label: 'OK', primary: true, onClick: this.onOK.bind(this) }
327
331
  ];
332
+ if (this.getDeviceRide().canEnforceSimulator()) {
333
+ return [
334
+ { label: 'Simulate', primary: true, onClick: this.onSimulate.bind(this) },
335
+ { label: 'Skip', primary: false, onClick: this.onSkip.bind(this) }
336
+ ];
337
+ }
328
338
  return [
329
339
  { label: 'Skip', primary: true, onClick: this.onSkip.bind(this) }
330
340
  ];
@@ -375,7 +385,6 @@ let DevicesPageService = (() => {
375
385
  const simulator = this.getDeviceConfiguration().getSimulatorAdapterId();
376
386
  this.getDevicePairing().prepareStart([simulator]);
377
387
  const prevContentPage = this.getPrevContentPage();
378
- const prevPage = this.getAppState().getState('prevPage');
379
388
  if (this.isPairingForRide)
380
389
  this.moveTo('/rideSimulate');
381
390
  else
@@ -400,6 +409,9 @@ let DevicesPageService = (() => {
400
409
  getDevicePairing() {
401
410
  return (0, pairing_1.useDevicePairing)();
402
411
  }
412
+ getDeviceRide() {
413
+ return (0, ride_1.useDeviceRide)();
414
+ }
403
415
  getDeviceConfiguration() {
404
416
  return (0, configuration_1.useDeviceConfiguration)();
405
417
  }
@@ -521,6 +521,8 @@ let RLVDisplayService = (() => {
521
521
  return fileName;
522
522
  if (fileName.startsWith('file:') || fileName.startsWith('http:') || fileName.startsWith('https:') || fileName.startsWith('/'))
523
523
  return fileName;
524
+ if (fileName.startsWith('content:'))
525
+ return fileName;
524
526
  return `./${fileName}`;
525
527
  }
526
528
  isLoopEnabled() {
@@ -90,10 +90,7 @@ let Route = (() => {
90
90
  return this.getLocalizedTitle(language);
91
91
  }
92
92
  getLocalizedTitle(language) {
93
- if (this._details)
94
- return (0, localization_1.getLocalizedText)(this._details.localizedTitle, language) ?? this._details.title;
95
- else
96
- return (0, localization_1.getLocalizedText)(this._description.localizedTitle, language) ?? this._description.title;
93
+ return (0, localization_1.getLocalizedText)(this._description.localizedTitle, language) ?? this._description.title;
97
94
  }
98
95
  clone() {
99
96
  const description = (0, clone_1.default)(this._description);
@@ -81,6 +81,7 @@ class EPMParser extends xml_1.XMLParser {
81
81
  return res.data;
82
82
  }
83
83
  catch (err) {
84
+ this.logger.logEvent({ message: 'could not load EPP file', reason: err.message });
84
85
  onError();
85
86
  }
86
87
  }
@@ -38,6 +38,7 @@ exports.GeometryParser = void 0;
38
38
  const api_1 = require("../../../api");
39
39
  const decorators_1 = require("../../../base/decorators");
40
40
  const utils_1 = require("../../../utils");
41
+ const utils_2 = require("./utils");
41
42
  let GeometryParser = (() => {
42
43
  let _instanceExtraInitializers = [];
43
44
  let _getLoader_decorators;
@@ -75,10 +76,8 @@ let GeometryParser = (() => {
75
76
  if (res.error) {
76
77
  onError();
77
78
  }
78
- if ((typeof res.data) === 'string') {
79
- return JSON.parse(res.data);
80
- }
81
- return res.data;
79
+ const cleaned = (0, utils_2.getUtf8Data)(res.data);
80
+ return JSON.parse(cleaned);
82
81
  }
83
82
  catch {
84
83
  onError();
@@ -24,6 +24,8 @@ const multixml_1 = require("./multixml");
24
24
  const bikelab_1 = require("./bikelab");
25
25
  const epm_1 = require("./epm");
26
26
  const TacxParser_1 = require("./tacx/TacxParser");
27
+ const utils_1 = require("./utils");
28
+ const gd_eventlog_1 = require("gd-eventlog");
27
29
  const useParsers = () => {
28
30
  const parsers = factory_1.ParserFactory.getInstance();
29
31
  if (!parsers.isInitialized()) {
@@ -38,6 +40,8 @@ const useParsers = () => {
38
40
  exports.useParsers = useParsers;
39
41
  class RouteParser {
40
42
  static async parse(info) {
43
+ const logger = new gd_eventlog_1.EventLogger('RouteParser');
44
+ (0, utils_1.fixIncorrectFileInfo)(info);
41
45
  const parsers = (0, exports.useParsers)();
42
46
  const formatParsers = parsers.suppertsExtension(info.ext);
43
47
  const promises = [];
@@ -51,8 +55,12 @@ class RouteParser {
51
55
  });
52
56
  const res = await Promise.allSettled(promises);
53
57
  const matching = res.map(promise => promise.status === 'fulfilled' ? promise.value : undefined).find(p => p !== undefined);
54
- if (matching)
55
- return await matching.parser.import(info, matching.data);
58
+ if (matching) {
59
+ logger.logEvent({ message: 'before import' });
60
+ const res = await matching.parser.import(info, matching.data);
61
+ logger.logEvent({ message: 'after import' });
62
+ return res;
63
+ }
56
64
  if (formatParsers.length === 0)
57
65
  throw new Error('no matching parser found');
58
66
  else {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MultipleXMLParser = void 0;
4
4
  const api_1 = require("../../../api");
5
5
  const xml_1 = require("../../../utils/xml");
6
+ const utils_1 = require("./utils");
6
7
  class MultipleXMLParser {
7
8
  parsers;
8
9
  constructor(classes) {
@@ -37,7 +38,8 @@ class MultipleXMLParser {
37
38
  if (res.error) {
38
39
  throw new Error('Could not open file');
39
40
  }
40
- const xml = await (0, xml_1.parseXml)(res.data);
41
+ const cleaned = (0, utils_1.getUtf8Data)(res.data);
42
+ const xml = await (0, xml_1.parseXml)(cleaned);
41
43
  return xml;
42
44
  }
43
45
  }
@@ -30,7 +30,9 @@ class TacxParser {
30
30
  if (res.error) {
31
31
  onError();
32
32
  }
33
- return res.data;
33
+ const buf = Buffer.isBuffer(res.data) ? res.data : Buffer.from(res.data, 'binary');
34
+ const data = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
35
+ return data;
34
36
  }
35
37
  catch {
36
38
  onError();
@@ -50,24 +52,18 @@ class TacxParser {
50
52
  if (file.ext === 'rlv') {
51
53
  const pgmfFile = (0, clone_1.default)(file);
52
54
  pgmfFile.ext = 'pgmf';
53
- pgmfFile.name = pgmfFile.name.replace('.rlv', '.pgmf');
54
- pgmfFile.filename = `${dir}${d}${pgmfFile.name}`;
55
- pgmfFile.url = `file:///${dir}${d}${pgmfFile.name}`;
56
- return {
57
- rlvFile: file,
58
- pgmfFile
59
- };
55
+ pgmfFile.base = pgmfFile.base.replace('.rlv', '.pgmf');
56
+ pgmfFile.filename = `${dir}${d}${pgmfFile.base}`;
57
+ pgmfFile.url = `file:///${dir}${d}${pgmfFile.base}`;
58
+ return { rlvFile: file, pgmfFile };
60
59
  }
61
60
  else if (file.ext === 'pgmf') {
62
61
  const rlvFile = (0, clone_1.default)(file);
63
62
  rlvFile.ext = 'rlv';
64
- rlvFile.name = rlvFile.name.replace('.pgmf', '.rlv');
65
- rlvFile.filename = `${dir}${d}${rlvFile.name}`;
66
- rlvFile.url = `file:///${dir}${d}${rlvFile.name}`;
67
- return {
68
- rlvFile,
69
- pgmfFile: file
70
- };
63
+ rlvFile.base = rlvFile.base.replace('.pgmf', '.rlv');
64
+ rlvFile.filename = `${dir}${d}${rlvFile.base}`;
65
+ rlvFile.url = `file:///${dir}${d}${rlvFile.base}`;
66
+ return { rlvFile, pgmfFile: file };
71
67
  }
72
68
  else {
73
69
  throw new Error(`Unsupported file type ${file.ext}`);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseInformations = exports.getReferencedFileInfo = exports.BinaryReader = void 0;
3
+ exports.getUtf8Data = exports.fixIncorrectFileInfo = exports.parseInformations = exports.getReferencedFileInfo = exports.BinaryReader = void 0;
4
4
  const api_1 = require("../../../api");
5
5
  class BinaryReader {
6
6
  pos;
@@ -102,6 +102,9 @@ const getReferencedFileInfo = (info, referenced, scheme = 'file') => {
102
102
  exports.getReferencedFileInfo = getReferencedFileInfo;
103
103
  const buildFromFile = (info, referenced) => {
104
104
  if (referenced.file) {
105
+ if (info.filename?.startsWith('content://')) {
106
+ return `${info.dir}${info.delimiter}${referenced.file}`;
107
+ }
105
108
  const fileName = info.filename?.replace(info.base, referenced.file);
106
109
  return `file:///${fileName}`;
107
110
  }
@@ -136,3 +139,42 @@ const parseInformations = (informations) => {
136
139
  });
137
140
  };
138
141
  exports.parseInformations = parseInformations;
142
+ const fixIncorrectFileInfo = (file) => {
143
+ if (!file.base) {
144
+ file.base = file.name;
145
+ file.name = file.base.replace(`.${file.ext}`, '');
146
+ }
147
+ };
148
+ exports.fixIncorrectFileInfo = fixIncorrectFileInfo;
149
+ const decodeUtf16Be = (data) => {
150
+ const swapped = Buffer.alloc(data.length);
151
+ for (let i = 0; i + 1 < data.length; i += 2) {
152
+ swapped[i] = data[i + 1];
153
+ swapped[i + 1] = data[i];
154
+ }
155
+ return Buffer.from(swapped.toString('utf16le')).toString('utf-8');
156
+ };
157
+ const getUtf8Data = (res) => {
158
+ const buf = Buffer.isBuffer(res) ? res : Buffer.from(res, 'binary');
159
+ if (buf[0] === 0xFE && buf[1] === 0xFF) {
160
+ return decodeUtf16Be(buf.subarray(2));
161
+ }
162
+ if (buf[0] === 0xFF && buf[1] === 0xFE) {
163
+ return Buffer.from(buf.subarray(2).toString('utf16le')).toString('utf-8');
164
+ }
165
+ if (buf[0] === 0xFD && buf[1] === 0xFD) {
166
+ if (buf[2] === 0x00 && buf[3] === 0x3C) {
167
+ return decodeUtf16Be(buf.subarray(2));
168
+ }
169
+ if (buf[2] === 0x3C && buf[3] === 0x00) {
170
+ return Buffer.from(buf.subarray(2).toString('utf16le')).toString('utf-8');
171
+ }
172
+ }
173
+ const str = typeof res === 'string' ? res : buf.toString('utf-8');
174
+ if (str.charCodeAt(0) === 0xFEFF)
175
+ return str.slice(1);
176
+ if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF)
177
+ return buf.subarray(3).toString('utf-8');
178
+ return str;
179
+ };
180
+ exports.getUtf8Data = getUtf8Data;
@@ -38,16 +38,19 @@ class XMLParser {
38
38
  const onError = () => {
39
39
  throw new Error('Could not open file: ' + (0, utils_2.getFileName)(file));
40
40
  };
41
+ let cleaned;
41
42
  const loader = (0, api_1.getBindings)().loader;
42
43
  try {
43
44
  const res = await loader.open(file);
44
45
  if (res.error) {
45
46
  onError();
46
47
  }
47
- const xml = await (0, xml_1.parseXml)(res.data);
48
+ const resData = (0, utils_1.getUtf8Data)(res.data);
49
+ const xml = await (0, xml_1.parseXml)(resData);
48
50
  return xml;
49
51
  }
50
52
  catch {
53
+ console.log('# failed', Buffer.from(cleaned ?? '').toString('hex'));
51
54
  onError();
52
55
  }
53
56
  }
@@ -93,6 +93,7 @@ let RouteLibraryScannerService = (() => {
93
93
  }
94
94
  done() {
95
95
  this.importProps = undefined;
96
+ this.scanResult = [];
96
97
  }
97
98
  getDisplayProps() {
98
99
  return { ...this.importProps };
@@ -130,7 +131,6 @@ let RouteLibraryScannerService = (() => {
130
131
  this.importProps.scanProgress = progress;
131
132
  })
132
133
  .on('scan-complete', () => {
133
- console.log('# scan-complete');
134
134
  this.importProps.phase = 'parsing';
135
135
  });
136
136
  return observer;
@@ -159,7 +159,6 @@ let RouteLibraryScannerService = (() => {
159
159
  }
160
160
  });
161
161
  observer.on('parse-complete', () => {
162
- console.log('# parse-complete');
163
162
  this.importProps.phase = 'selecting';
164
163
  });
165
164
  return observer;
@@ -193,7 +192,8 @@ let RouteLibraryScannerService = (() => {
193
192
  }
194
193
  cancel() {
195
194
  this.isCancelled = true;
196
- this.importProps.phase = 'landing';
195
+ this.done();
196
+ this.prepare();
197
197
  }
198
198
  async importRoute(fileInfo, observer) {
199
199
  await (0, sleep_1.sleep)(0);
@@ -313,7 +313,6 @@ let RouteLibraryScannerService = (() => {
313
313
  if (!this.isCancelled) {
314
314
  const routeAnnouncement = await this.buildDiscoveredRoute(file, files, uri, folderName, parsers);
315
315
  discoveredCount.value++;
316
- console.log('added', routeAnnouncement, this.scanResult.length);
317
316
  observer.emit('scan-result', routeAnnouncement);
318
317
  this.scanResult.push(routeAnnouncement);
319
318
  }
@@ -373,57 +372,95 @@ let RouteLibraryScannerService = (() => {
373
372
  observer.emit('parse-complete');
374
373
  }
375
374
  async _parseTarget(target, service, observer, idx) {
376
- if (service.existsBySourceUri(target.controlFileUri)) {
377
- observer.emit('parse-result', {
378
- alreadyImported: true,
379
- route: service.getBySourceUri(target.controlFileUri),
380
- folderUri: target.folderUri,
381
- controlFileUri: target.controlFileUri,
382
- format: target.format
383
- });
384
- return;
385
- }
375
+ this.logEvent({ message: '_parseTarget', target: target.controlFileUri });
386
376
  let result;
387
377
  const file = this.buildFileInfo(target.controlFileUri, target.format);
388
378
  try {
389
- try {
390
- result = await parsers_1.RouteParser.parse(file);
391
- }
392
- catch (err) {
393
- throw new Error(`Could not parse: [${err.message}]`);
379
+ this.logEvent({ message: '_parseTarget:parse', target: target.controlFileUri });
380
+ result = await parsers_1.RouteParser.parse(file);
381
+ this.logEvent({ message: '_parseTarget:parse done', target: target.controlFileUri });
382
+ const route = new route_1.Route(result.data, result.details);
383
+ const existing = this.importProps.routes.find(ri => ri.id === route.description.id);
384
+ if (existing) {
385
+ result = undefined;
386
+ throw new Error(`Duplicate of ${existing.label}`);
394
387
  }
395
388
  if (result.data.hasVideo) {
396
- this.validateVideoUrl(result.details, target.folderUri, target.files);
389
+ this.validateVideoUrl(route, target.folderUri, target.files);
397
390
  }
398
- observer.emit('parse-result', {
391
+ const parsed = {
399
392
  alreadyImported: false,
400
- route: new route_1.Route(result.data, result.details),
393
+ route,
401
394
  folderUri: target.folderUri,
402
395
  controlFileUri: target.controlFileUri,
403
396
  format: target.format
404
- });
397
+ };
398
+ if (service.getRoute(route.description.id)) {
399
+ parsed.alreadyImported = true;
400
+ }
401
+ observer.emit('parse-result', parsed);
405
402
  }
406
403
  catch (err) {
407
- observer.emit('parse-result', {
404
+ const parsed = {
408
405
  alreadyImported: false,
409
406
  route: result ? new route_1.Route(result.data, result.details) : undefined,
410
407
  folderUri: target.folderUri,
411
408
  controlFileUri: target.controlFileUri,
412
409
  format: target.format,
413
410
  parseError: err?.message ?? String(err)
414
- });
411
+ };
412
+ this.logEvent({ message: 'could not parse route file', file: file.base, reason: err.message, stack: err.stack });
413
+ observer.emit('parse-result', parsed);
415
414
  }
416
415
  }
417
- validateVideoUrl(routeDetail, folderUri, folderFiles) {
416
+ validateVideoUrl(route, folderUri, folderFiles) {
417
+ const routeDetail = route.details;
418
+ const routeDescr = route.description;
418
419
  if (this.isMobile()) {
419
420
  if (routeDetail.video.format === 'avi') {
420
- throw new Error('AVI video not supported');
421
+ const hasUrl = routeDetail.video.url != null;
422
+ const url = routeDetail.video.url ?? routeDetail.video.file;
423
+ this.logEvent({ message: '# here', url });
424
+ try {
425
+ if (url) {
426
+ const mp4Url = this.findMatchingMp4(url, folderFiles);
427
+ if (mp4Url) {
428
+ routeDetail.video.format = 'mp4';
429
+ if (hasUrl)
430
+ routeDetail.video.url = mp4Url;
431
+ else
432
+ routeDetail.video.file = mp4Url;
433
+ routeDescr.videoFormat = 'mp4';
434
+ }
435
+ else {
436
+ throw new Error('AVI video not supported');
437
+ }
438
+ }
439
+ else {
440
+ this.logEvent({ message: 'video file not found', video: routeDetail.video });
441
+ throw new Error('no video found');
442
+ }
443
+ }
444
+ catch (err) {
445
+ this.logEvent({ message: 'video check failed', url });
446
+ throw err;
447
+ }
421
448
  }
422
449
  }
423
450
  if (routeDetail.video.file) {
424
451
  routeDetail.video.file = this.resolveVideoUri(routeDetail.video.file, folderUri, folderFiles);
425
452
  }
426
453
  }
454
+ findMatchingMp4(videoUrl, folderFiles) {
455
+ const path = this.getBindings().path;
456
+ const fileName = path.parse(videoUrl)?.base;
457
+ const target = fileName.replace('.avi', '.mp4');
458
+ const folderFile = folderFiles.find(file => file.name === target);
459
+ if (!folderFile)
460
+ return;
461
+ const info = path.parse(videoUrl);
462
+ return videoUrl.replace(info.base, folderFile.name);
463
+ }
427
464
  async _ingest(routes, observer) {
428
465
  await (0, utils_1.waitNextTick)();
429
466
  const service = this.getRouteList();
@@ -501,7 +538,7 @@ let RouteLibraryScannerService = (() => {
501
538
  name,
502
539
  dir,
503
540
  ext,
504
- delimiter: uri.startsWith('content://') ? '%2F' : '/'
541
+ delimiter: uri?.startsWith('content://') ? '%2F' : '/'
505
542
  };
506
543
  }
507
544
  buildRouteDisplayItem(parsed, observer) {
@@ -127,6 +127,7 @@ let RouteListService = (() => {
127
127
  currentView;
128
128
  stats;
129
129
  isListUpdatePaused = false;
130
+ cardLookup = {};
130
131
  constructor() {
131
132
  super('RouteList');
132
133
  this.myRoutes = new myroutes_1.MyRoutes('myRoutes', 'My Routes');
@@ -324,15 +325,17 @@ let RouteListService = (() => {
324
325
  }
325
326
  try {
326
327
  const filters = requestedFilters ?? this.filters;
327
- let routes = Array.from(this.getAllSearchCards().map(c => c.getDisplayProperties()));
328
+ const allCards = this.getAllSearchCards();
329
+ let routes = allCards.map(c => c.getDisplayProperties());
328
330
  routes.sort((a, b) => a.title > b.title ? 1 : -1);
329
331
  const units = this.getUnitConverter().getDefaultUnits();
330
332
  if (!filters) {
331
333
  const cards = routes.map(r => this.getCard(r.id));
332
334
  return { routes, cards, filters, observer: this.observer, units };
333
335
  }
334
- if (!filters.includeDeleted)
336
+ if (!filters.includeDeleted) {
335
337
  routes = routes.filter(r => !r?.isDeleted);
338
+ }
336
339
  routes = this.applyTitleFilter(filters, routes);
337
340
  routes = this.applyDistanceFilter(filters, routes);
338
341
  routes = this.applyElevationFilter(filters, routes);
@@ -340,7 +343,8 @@ let RouteListService = (() => {
340
343
  routes = this.applyContentTypeFilter(filters, routes);
341
344
  routes = this.applyRouteTypeFilter(filters, routes);
342
345
  routes = this.applySourceFilter(filters, routes);
343
- const cards = routes.map(r => this.getCard(r.id));
346
+ const routeIdSet = new Set(routes.map(r => r.id));
347
+ const cards = allCards.filter(c => routeIdSet.has(c.getId()));
344
348
  this.setListTop('list', 0);
345
349
  this.setListTop('tiles', 0);
346
350
  return { routes, cards, filters, observer: this.observer, units };
@@ -690,6 +694,7 @@ let RouteListService = (() => {
690
694
  card.save();
691
695
  card.enableDelete();
692
696
  this.myRoutes.add(card, true);
697
+ this.cardLookup[route.description.id] = { card, list: this.myRoutes };
693
698
  if (importCard) {
694
699
  this.myRoutes.remove(importCard);
695
700
  }
@@ -820,6 +825,9 @@ let RouteListService = (() => {
820
825
  }
821
826
  }
822
827
  addRoute(route, source = 'system') {
828
+ if (this.getRoute(route.description.id)) {
829
+ return;
830
+ }
823
831
  this.routes.push(route);
824
832
  if (route.description?.isDeleted) {
825
833
  return;
@@ -831,12 +839,14 @@ let RouteListService = (() => {
831
839
  if (!route.description.country) {
832
840
  route.updateCountryFromPoints()
833
841
  .then(() => {
842
+ this.logEvent({ message: 'route updated (country)', route: route.title });
834
843
  this.db.save(route, false);
835
844
  });
836
845
  }
837
846
  }
838
847
  const list = this.selectList(route);
839
848
  const card = new RouteCard_1.RouteCard(route, { list });
849
+ this.cardLookup[route.description.id] = { card, list };
840
850
  card.verify();
841
851
  list.add(card);
842
852
  if (list.getId() === 'myRoutes')
@@ -850,10 +860,9 @@ let RouteListService = (() => {
850
860
  }
851
861
  }
852
862
  async addFromApi(route) {
853
- const existing = this.findCard(route);
854
- if (existing)
855
- return;
863
+ this.logEvent({ message: 'add route from Api', route: route.title });
856
864
  this.addRoute(route, 'system');
865
+ this.logEvent({ message: 'add route from Api done', route: route.title });
857
866
  }
858
867
  async update(route, source = 'user') {
859
868
  const existing = this.findCard(route);
@@ -870,16 +879,21 @@ let RouteListService = (() => {
870
879
  this.startSync();
871
880
  }
872
881
  async preloadDetails() {
882
+ this.logEvent({ message: 'preload route details: searchRepo' });
873
883
  const { cards = [] } = this.searchRepo();
884
+ this.logEvent({ message: 'preload route details: searchRepo done' });
874
885
  const promises = [];
875
886
  const loadDetails = (card) => {
887
+ this.logEvent({ message: 'preload route details', route: card?.getData()?.title });
876
888
  return this.db.getDetails(card.getId())
877
889
  .then(details => {
890
+ this.logEvent({ message: 'preload route details done', route: card?.getData()?.title });
878
891
  card.setRouteData(details);
879
892
  const route = card.getData();
880
893
  if (!route.description.country) {
881
894
  route.updateCountryFromPoints()
882
895
  .then(() => {
896
+ this.logEvent({ message: 'preload route updated (country)', route: card?.getData()?.title });
883
897
  this.db.save(route, false);
884
898
  });
885
899
  }
@@ -981,6 +995,9 @@ let RouteListService = (() => {
981
995
  });
982
996
  }
983
997
  async checkUIUpdateWithNoRepoStats() {
998
+ if (this.isMobile())
999
+ return;
1000
+ this.logEvent({ message: 'checkUIUpdateWithNoRepoStats' });
984
1001
  const repoUpdate = this.getRepoUpdates();
985
1002
  if (this.routes.length > 0 && repoUpdate.initial === undefined) {
986
1003
  const ts = Date.now() - 60000;
@@ -991,6 +1008,7 @@ let RouteListService = (() => {
991
1008
  this.updateRepoStats(ts);
992
1009
  await (0, sleep_1.sleep)(5);
993
1010
  }
1011
+ this.logEvent({ message: 'checkUIUpdateWithNoRepoStats done' });
994
1012
  }
995
1013
  getRepoUpdates() {
996
1014
  try {
@@ -1005,23 +1023,33 @@ let RouteListService = (() => {
1005
1023
  }
1006
1024
  async loadRoutesFromRepo() {
1007
1025
  return new Promise(done => {
1026
+ this.logEvent({ message: 'loadRoutesFromRepo start' });
1008
1027
  const observer = this.db.load();
1009
1028
  const add = this.addRoute.bind(this);
1010
1029
  const update = this.update.bind(this);
1030
+ const completed = () => {
1031
+ this.logEvent({ message: 'loadRoutesFromRepo done' });
1032
+ done();
1033
+ };
1011
1034
  observer.on('route.added', add);
1012
1035
  observer.on('route.updated', update);
1013
- observer.on('done', done);
1036
+ observer.on('done', completed);
1014
1037
  });
1015
1038
  }
1016
1039
  async loadRoutesFromApi() {
1017
1040
  return new Promise(done => {
1041
+ this.logEvent({ message: 'loadRoutesFromApi start' });
1018
1042
  const observer = this.api.load();
1019
1043
  const add = this.addFromApi.bind(this);
1020
1044
  const update = this.update.bind(this);
1045
+ const completed = () => {
1046
+ this.logEvent({ message: 'loadRoutesFromApi done' });
1047
+ done();
1048
+ };
1021
1049
  observer.on('route.added', add);
1022
1050
  observer.on('route.updated', update);
1023
- observer.on('loaded', done);
1024
- observer.on('done', done);
1051
+ observer.on('loaded', completed);
1052
+ observer.on('done', completed);
1025
1053
  });
1026
1054
  }
1027
1055
  async loadRouteDetails(route, id) {
@@ -1232,6 +1260,10 @@ let RouteListService = (() => {
1232
1260
  if (!id && !legacyId) {
1233
1261
  return;
1234
1262
  }
1263
+ const res = this.cardLookup[id];
1264
+ if (res?.card) {
1265
+ return res;
1266
+ }
1235
1267
  let card = this.myRoutes.getCards().find(c => c.getData()?.description?.id === id || c.getData()?.description?.legacyId === id);
1236
1268
  if (card)
1237
1269
  return { card, list: this.myRoutes };
@@ -262,6 +262,9 @@ let RoutesPageService = (() => {
262
262
  getImportDisplayProps() {
263
263
  return this.getRouteLibraryScanner().getDisplayProps();
264
264
  }
265
+ onImportCancelled() {
266
+ this.getRouteLibraryScanner().done();
267
+ }
265
268
  onImportClosed() {
266
269
  try {
267
270
  this.showImportDialog = false;
@@ -290,8 +293,9 @@ let RoutesPageService = (() => {
290
293
  try {
291
294
  const service = this.getRouteList();
292
295
  const card = service.getCard(id);
293
- if (card)
296
+ if (card) {
294
297
  card.delete();
298
+ }
295
299
  }
296
300
  catch (err) {
297
301
  this.logError(err, 'onDelete');