incyclist-services 1.7.54 → 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 (45) 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/incyclist.js +4 -4
  7. package/lib/cjs/routes/base/parsers/index.js +10 -2
  8. package/lib/cjs/routes/base/parsers/multixml.js +3 -1
  9. package/lib/cjs/routes/base/parsers/tacx/TacxParser.js +11 -15
  10. package/lib/cjs/routes/base/parsers/utils.js +50 -6
  11. package/lib/cjs/routes/base/parsers/xml.js +4 -1
  12. package/lib/cjs/routes/library/service.js +105 -41
  13. package/lib/cjs/routes/list/service.js +41 -9
  14. package/lib/cjs/routes/page/service.js +11 -3
  15. package/lib/cjs/video/VideoSyncHelper.js +14 -3
  16. package/lib/cjs/workouts/base/parsers/incyclist/Json.js +3 -1
  17. package/lib/cjs/workouts/base/parsers/intervals/parser.js +3 -1
  18. package/lib/cjs/workouts/base/parsers/zwo/zwo.js +3 -1
  19. package/lib/esm/devices/page/service.js +13 -1
  20. package/lib/esm/ride/route/RLVDisplayService.js +2 -0
  21. package/lib/esm/routes/base/model/route.js +1 -4
  22. package/lib/esm/routes/base/parsers/epm.js +1 -0
  23. package/lib/esm/routes/base/parsers/geometry.js +3 -4
  24. package/lib/esm/routes/base/parsers/incyclist.js +4 -4
  25. package/lib/esm/routes/base/parsers/index.js +10 -2
  26. package/lib/esm/routes/base/parsers/multixml.js +3 -1
  27. package/lib/esm/routes/base/parsers/tacx/TacxParser.js +11 -15
  28. package/lib/esm/routes/base/parsers/utils.js +47 -5
  29. package/lib/esm/routes/base/parsers/xml.js +5 -2
  30. package/lib/esm/routes/library/service.js +105 -41
  31. package/lib/esm/routes/list/service.js +41 -9
  32. package/lib/esm/routes/page/service.js +11 -3
  33. package/lib/esm/video/VideoSyncHelper.js +14 -3
  34. package/lib/esm/workouts/base/parsers/incyclist/Json.js +3 -1
  35. package/lib/esm/workouts/base/parsers/intervals/parser.js +3 -1
  36. package/lib/esm/workouts/base/parsers/zwo/zwo.js +3 -1
  37. package/lib/types/api/fs/index.d.ts +1 -1
  38. package/lib/types/api/repository/types.d.ts +1 -2
  39. package/lib/types/devices/page/service.d.ts +1 -0
  40. package/lib/types/routes/base/parsers/utils.d.ts +2 -0
  41. package/lib/types/routes/library/service.d.ts +1 -0
  42. package/lib/types/routes/library/types.d.ts +3 -0
  43. package/lib/types/routes/list/service.d.ts +4 -0
  44. package/lib/types/routes/page/service.d.ts +1 -0
  45. 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();
@@ -30,8 +30,8 @@ class IncyclistXMLParser extends xml_1.XMLParser {
30
30
  async loadPoints(context) {
31
31
  const { data, fileInfo, route } = context;
32
32
  const gpxFile = { ...fileInfo };
33
- const xmlName = fileInfo.name;
34
- const fileName = data['gpx-file-path'] ?? xmlName.replace('xml', 'gpx');
33
+ const xmlBase = fileInfo.base;
34
+ const fileName = data['gpx-file-path'] ?? xmlBase.replace('.xml', '.gpx');
35
35
  if (fileName.startsWith('file') || fileName.startsWith('/') || fileName.startsWith('\\') || fileName.startsWith('.')) {
36
36
  gpxFile.type = 'file';
37
37
  gpxFile.filename = fileName;
@@ -41,10 +41,10 @@ class IncyclistXMLParser extends xml_1.XMLParser {
41
41
  gpxFile.url = fileName;
42
42
  }
43
43
  else if (fileInfo.type === 'url') {
44
- gpxFile.url = gpxFile.url.replace(xmlName, fileName);
44
+ gpxFile.url = gpxFile.url.replace(xmlBase, fileName);
45
45
  }
46
46
  else {
47
- gpxFile.filename = gpxFile.filename.replace(xmlName, fileName);
47
+ gpxFile.filename = gpxFile.filename.replace(xmlBase, fileName);
48
48
  }
49
49
  try {
50
50
  let points;
@@ -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;
@@ -73,7 +73,7 @@ class BinaryReader {
73
73
  exports.BinaryReader = BinaryReader;
74
74
  const getReferencedFileInfo = (info, referenced, scheme = 'file') => {
75
75
  if (info.type !== 'url') {
76
- return buildFromFile(info, referenced, scheme);
76
+ return buildFromFile(info, referenced);
77
77
  }
78
78
  if (referenced.url) {
79
79
  return referenced.url;
@@ -81,6 +81,9 @@ const getReferencedFileInfo = (info, referenced, scheme = 'file') => {
81
81
  if (!referenced.file) {
82
82
  return;
83
83
  }
84
+ if (referenced.file && info.filename?.startsWith('content://')) {
85
+ return `${info.dir}${info.delimiter}${referenced.file}`;
86
+ }
84
87
  const targetFileName = referenced.file;
85
88
  const regex = /([\\/])/g;
86
89
  if (targetFileName.startsWith('http://') || targetFileName.startsWith('https://')) {
@@ -97,9 +100,12 @@ const getReferencedFileInfo = (info, referenced, scheme = 'file') => {
97
100
  }
98
101
  };
99
102
  exports.getReferencedFileInfo = getReferencedFileInfo;
100
- const buildFromFile = (info, referenced, scheme = 'file') => {
103
+ const buildFromFile = (info, referenced) => {
101
104
  if (referenced.file) {
102
- const fileName = info.filename?.replace(info.name, referenced.file);
105
+ if (info.filename?.startsWith('content://')) {
106
+ return `${info.dir}${info.delimiter}${referenced.file}`;
107
+ }
108
+ const fileName = info.filename?.replace(info.base, referenced.file);
103
109
  return `file:///${fileName}`;
104
110
  }
105
111
  return referenced.url;
@@ -107,9 +113,8 @@ const buildFromFile = (info, referenced, scheme = 'file') => {
107
113
  const buildAbsolutePathTarget = (fileName, info, scheme) => {
108
114
  const inputUrl = info.url;
109
115
  if (inputUrl.startsWith('incyclist:') || inputUrl.startsWith('file:')) {
110
- const target = {};
111
116
  const parts = inputUrl.split('://');
112
- const targetPath = parts[1].replace(info.name, fileName);
117
+ const targetPath = parts[1].replace(info.base, fileName);
113
118
  return `${scheme}://${targetPath}`;
114
119
  }
115
120
  };
@@ -134,3 +139,42 @@ const parseInformations = (informations) => {
134
139
  });
135
140
  };
136
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,9 +93,10 @@ let RouteLibraryScannerService = (() => {
93
93
  }
94
94
  done() {
95
95
  this.importProps = undefined;
96
+ this.scanResult = [];
96
97
  }
97
98
  getDisplayProps() {
98
- return this.importProps;
99
+ return { ...this.importProps };
99
100
  }
100
101
  importSingle(fileInfo) {
101
102
  const observer = new types_1.Observer();
@@ -125,8 +126,12 @@ let RouteLibraryScannerService = (() => {
125
126
  this.logError(err, 'scan', { uri: folderInfo.uri });
126
127
  observer.emit('error', err.message);
127
128
  });
128
- observer.on('scan-progress', (progress) => {
129
+ observer
130
+ .on('scan-progress', (progress) => {
129
131
  this.importProps.scanProgress = progress;
132
+ })
133
+ .on('scan-complete', () => {
134
+ this.importProps.phase = 'parsing';
130
135
  });
131
136
  return observer;
132
137
  }
@@ -143,7 +148,15 @@ let RouteLibraryScannerService = (() => {
143
148
  this.importProps.parseProgress = { parsed, total };
144
149
  });
145
150
  observer.on('parse-result', (route) => {
146
- this.importProps.routes.push(this.buildRouteDisplayItem(route));
151
+ const idx = this.importProps.routes.findIndex(r => r.id === route.controlFileUri);
152
+ if (idx !== -1) {
153
+ const observer = this.importProps.routes[idx].observer;
154
+ const displayProps = this.buildRouteDisplayItem(route, observer);
155
+ this.importProps.routes[idx] = displayProps;
156
+ if (observer) {
157
+ observer.emit('updated', displayProps);
158
+ }
159
+ }
147
160
  });
148
161
  observer.on('parse-complete', () => {
149
162
  this.importProps.phase = 'selecting';
@@ -179,7 +192,8 @@ let RouteLibraryScannerService = (() => {
179
192
  }
180
193
  cancel() {
181
194
  this.isCancelled = true;
182
- this.importProps.phase = 'landing';
195
+ this.done();
196
+ this.prepare();
183
197
  }
184
198
  async importRoute(fileInfo, observer) {
185
199
  await (0, sleep_1.sleep)(0);
@@ -283,7 +297,7 @@ let RouteLibraryScannerService = (() => {
283
297
  const primaryFiles = files.filter((f) => {
284
298
  const name = typeof (f) === 'string' ? f : f.name;
285
299
  const ext = this.getExtension(name);
286
- return ext && parsers.isPrimaryExtension(ext);
300
+ return ext && ext !== 'gpx' && parsers.isPrimaryExtension(ext);
287
301
  }).map((f) => {
288
302
  if (typeof f === 'string') {
289
303
  return {
@@ -335,68 +349,118 @@ let RouteLibraryScannerService = (() => {
335
349
  const service = this.getRouteList();
336
350
  const targets = scannedRoutes.filter(r => !r.scanError);
337
351
  const total = targets.length;
352
+ targets.forEach(target => {
353
+ const file = this.buildFileInfo(target.controlFileUri, target.format);
354
+ this.importProps.routes.push({
355
+ format: target.format,
356
+ importable: false,
357
+ label: file.base,
358
+ id: target.controlFileUri,
359
+ alreadyImported: false,
360
+ observer: new types_1.Observer()
361
+ });
362
+ });
363
+ observer.emit('parse-start');
338
364
  for (let i = 0; i < targets.length; i++) {
339
365
  if (this.isCancelled)
340
366
  continue;
341
367
  const parsed = i + 1;
342
368
  const target = targets[i];
343
369
  observer.emit('parse-progress', { current: parsed, parsed, total, currentFolder: target.folderName });
344
- await this._parseTarget(target, service, observer);
370
+ await this._parseTarget(target, service, observer, i);
345
371
  }
346
372
  observer.emit('parse-complete');
347
373
  }
348
- async _parseTarget(target, service, observer) {
349
- if (service.existsBySourceUri(target.controlFileUri)) {
350
- observer.emit('parse-result', {
351
- alreadyImported: true,
352
- route: service.getBySourceUri(target.controlFileUri),
353
- folderUri: target.folderUri,
354
- controlFileUri: target.controlFileUri,
355
- format: target.format
356
- });
357
- return;
358
- }
374
+ async _parseTarget(target, service, observer, idx) {
375
+ this.logEvent({ message: '_parseTarget', target: target.controlFileUri });
359
376
  let result;
360
377
  const file = this.buildFileInfo(target.controlFileUri, target.format);
361
378
  try {
362
- try {
363
- result = await parsers_1.RouteParser.parse(file);
364
- }
365
- catch (err) {
366
- 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}`);
367
387
  }
368
388
  if (result.data.hasVideo) {
369
- this.validateVideoUrl(result.details, target.folderUri, target.files);
389
+ this.validateVideoUrl(route, target.folderUri, target.files);
370
390
  }
371
- observer.emit('parse-result', {
391
+ const parsed = {
372
392
  alreadyImported: false,
373
- route: new route_1.Route(result.data, result.details),
393
+ route,
374
394
  folderUri: target.folderUri,
375
395
  controlFileUri: target.controlFileUri,
376
396
  format: target.format
377
- });
397
+ };
398
+ if (service.getRoute(route.description.id)) {
399
+ parsed.alreadyImported = true;
400
+ }
401
+ observer.emit('parse-result', parsed);
378
402
  }
379
403
  catch (err) {
380
- observer.emit('parse-result', {
404
+ const parsed = {
381
405
  alreadyImported: false,
382
406
  route: result ? new route_1.Route(result.data, result.details) : undefined,
383
407
  folderUri: target.folderUri,
384
408
  controlFileUri: target.controlFileUri,
385
409
  format: target.format,
386
410
  parseError: err?.message ?? String(err)
387
- });
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);
388
414
  }
389
415
  }
390
- validateVideoUrl(routeDetail, folderUri, folderFiles) {
416
+ validateVideoUrl(route, folderUri, folderFiles) {
417
+ const routeDetail = route.details;
418
+ const routeDescr = route.description;
391
419
  if (this.isMobile()) {
392
420
  if (routeDetail.video.format === 'avi') {
393
- 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
+ }
394
448
  }
395
449
  }
396
450
  if (routeDetail.video.file) {
397
451
  routeDetail.video.file = this.resolveVideoUri(routeDetail.video.file, folderUri, folderFiles);
398
452
  }
399
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
+ }
400
464
  async _ingest(routes, observer) {
401
465
  await (0, utils_1.waitNextTick)();
402
466
  const service = this.getRouteList();
@@ -465,37 +529,37 @@ let RouteLibraryScannerService = (() => {
465
529
  }
466
530
  }
467
531
  buildFileInfo(uri, ext) {
468
- const lastSlash = Math.max(uri.lastIndexOf('/'), uri.lastIndexOf('\\'));
469
- const dir = uri.slice(0, lastSlash);
470
- const base = uri.slice(lastSlash + 1);
471
- const name = base.slice(0, base.length - ext.length - 1);
532
+ const { dir, base, name } = this.getBindings().path.parse(uri);
472
533
  return {
473
- type: 'url',
534
+ type: 'file',
474
535
  url: uri,
475
536
  filename: uri,
476
537
  base,
477
538
  name,
478
539
  dir,
479
540
  ext,
480
- delimiter: '/'
541
+ delimiter: uri?.startsWith('content://') ? '%2F' : '/'
481
542
  };
482
543
  }
483
- buildRouteDisplayItem(parsed) {
484
- const { route, alreadyImported, parseError, format } = parsed;
544
+ buildRouteDisplayItem(parsed, observer) {
545
+ const { route, alreadyImported, parseError, format, controlFileUri } = parsed;
485
546
  const descr = route?.description ?? {};
486
547
  const [C, U] = this.getUnitConversionShortcuts();
487
548
  const distance = descr.distance === undefined ? undefined : {
488
549
  value: C(descr.distance, 'distance', { digits: 1 }),
489
550
  unit: U('distance')
490
551
  };
552
+ const path = this.getBindings().path;
553
+ const info = path.parse(controlFileUri);
491
554
  return {
492
- id: route.description.id,
555
+ id: route?.description?.id ?? info?.base,
493
556
  distance,
494
- label: route.title,
557
+ label: route?.title ?? info?.base,
495
558
  alreadyImported,
496
559
  importable: parseError == null,
497
560
  format,
498
- errorReason: parseError
561
+ errorReason: parseError,
562
+ observer: observer ?? new types_1.Observer()
499
563
  };
500
564
  }
501
565
  getCompanionExts(parsers, primaryExt) {