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.
- package/lib/cjs/devices/page/service.js +13 -1
- package/lib/cjs/ride/route/RLVDisplayService.js +2 -0
- package/lib/cjs/routes/base/model/route.js +1 -4
- package/lib/cjs/routes/base/parsers/epm.js +1 -0
- package/lib/cjs/routes/base/parsers/geometry.js +3 -4
- package/lib/cjs/routes/base/parsers/incyclist.js +4 -4
- package/lib/cjs/routes/base/parsers/index.js +10 -2
- package/lib/cjs/routes/base/parsers/multixml.js +3 -1
- package/lib/cjs/routes/base/parsers/tacx/TacxParser.js +11 -15
- package/lib/cjs/routes/base/parsers/utils.js +50 -6
- package/lib/cjs/routes/base/parsers/xml.js +4 -1
- package/lib/cjs/routes/library/service.js +105 -41
- package/lib/cjs/routes/list/service.js +41 -9
- package/lib/cjs/routes/page/service.js +11 -3
- package/lib/cjs/video/VideoSyncHelper.js +14 -3
- package/lib/cjs/workouts/base/parsers/incyclist/Json.js +3 -1
- package/lib/cjs/workouts/base/parsers/intervals/parser.js +3 -1
- package/lib/cjs/workouts/base/parsers/zwo/zwo.js +3 -1
- package/lib/esm/devices/page/service.js +13 -1
- package/lib/esm/ride/route/RLVDisplayService.js +2 -0
- package/lib/esm/routes/base/model/route.js +1 -4
- package/lib/esm/routes/base/parsers/epm.js +1 -0
- package/lib/esm/routes/base/parsers/geometry.js +3 -4
- package/lib/esm/routes/base/parsers/incyclist.js +4 -4
- package/lib/esm/routes/base/parsers/index.js +10 -2
- package/lib/esm/routes/base/parsers/multixml.js +3 -1
- package/lib/esm/routes/base/parsers/tacx/TacxParser.js +11 -15
- package/lib/esm/routes/base/parsers/utils.js +47 -5
- package/lib/esm/routes/base/parsers/xml.js +5 -2
- package/lib/esm/routes/library/service.js +105 -41
- package/lib/esm/routes/list/service.js +41 -9
- package/lib/esm/routes/page/service.js +11 -3
- package/lib/esm/video/VideoSyncHelper.js +14 -3
- package/lib/esm/workouts/base/parsers/incyclist/Json.js +3 -1
- package/lib/esm/workouts/base/parsers/intervals/parser.js +3 -1
- package/lib/esm/workouts/base/parsers/zwo/zwo.js +3 -1
- package/lib/types/api/fs/index.d.ts +1 -1
- package/lib/types/api/repository/types.d.ts +1 -2
- package/lib/types/devices/page/service.d.ts +1 -0
- package/lib/types/routes/base/parsers/utils.d.ts +2 -0
- package/lib/types/routes/library/service.d.ts +1 -0
- package/lib/types/routes/library/types.d.ts +3 -0
- package/lib/types/routes/list/service.d.ts +4 -0
- package/lib/types/routes/page/service.d.ts +1 -0
- 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
|
-
|
|
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);
|
|
@@ -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
|
-
|
|
79
|
-
|
|
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
|
|
34
|
-
const fileName = data['gpx-file-path'] ??
|
|
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(
|
|
44
|
+
gpxFile.url = gpxFile.url.replace(xmlBase, fileName);
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
|
-
gpxFile.filename = gpxFile.filename.replace(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
54
|
-
pgmfFile.filename = `${dir}${d}${pgmfFile.
|
|
55
|
-
pgmfFile.url = `file:///${dir}${d}${pgmfFile.
|
|
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.
|
|
65
|
-
rlvFile.filename = `${dir}${d}${rlvFile.
|
|
66
|
-
rlvFile.url = `file:///${dir}${d}${rlvFile.
|
|
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
|
|
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
|
|
103
|
+
const buildFromFile = (info, referenced) => {
|
|
101
104
|
if (referenced.file) {
|
|
102
|
-
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
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(
|
|
389
|
+
this.validateVideoUrl(route, target.folderUri, target.files);
|
|
370
390
|
}
|
|
371
|
-
|
|
391
|
+
const parsed = {
|
|
372
392
|
alreadyImported: false,
|
|
373
|
-
route
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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
|
|
555
|
+
id: route?.description?.id ?? info?.base,
|
|
493
556
|
distance,
|
|
494
|
-
label: route
|
|
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) {
|