incyclist-services 1.0.62 → 1.0.63

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.
@@ -27,7 +27,7 @@ export interface FileLoaderResult {
27
27
  }
28
28
  export interface FileInfo {
29
29
  type: 'url' | 'file';
30
- url: string;
30
+ url?: string;
31
31
  name: string;
32
32
  dir: string;
33
33
  ext: string;
@@ -175,7 +175,6 @@ class DeviceAccessService extends events_1.default {
175
175
  this.connect(name); });
176
176
  return;
177
177
  }
178
- console.log('~~~ DEBUG:connect if', ifaceName, this.interfaces[ifaceName]);
179
178
  if (((_a = this.interfaces[ifaceName]) === null || _a === void 0 ? void 0 : _a.enabled) === false)
180
179
  return;
181
180
  const impl = this.getInterface(ifaceName);
@@ -189,6 +188,7 @@ class DeviceAccessService extends events_1.default {
189
188
  return true;
190
189
  this.interfaces[ifaceName].state = 'connecting';
191
190
  this.emit('interface-changed', ifaceName, Object.assign(Object.assign({}, this.interfaces[ifaceName]), { state: 'connecting' }));
191
+ yield impl.disconnect();
192
192
  const connected = yield impl.connect();
193
193
  const state = connected ? 'connected' : 'disconnected';
194
194
  this.interfaces[ifaceName].state = state;
@@ -54,6 +54,7 @@ export interface InternalPairingState extends PairingState {
54
54
  check?: {
55
55
  preparing?: number;
56
56
  promise?: Promise<boolean>;
57
+ to?: NodeJS.Timeout;
57
58
  };
58
59
  scan?: {
59
60
  preparing?: number;
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { DeviceAccessService } from "../access/service";
2
3
  import { AdapterInfo, CapabilityInformation, DeviceConfigurationInfo, DeviceConfigurationService, IncyclistDeviceSettings, InterfaceSetting } from "../configuration";
3
4
  import { CapabilityData, DevicePairingData, DevicePairingStatus, DeviceSelectState, InternalPairingState, PairingProps, PairingSettings, PairingState } from "./model";
@@ -32,6 +33,7 @@ export declare class DevicePairingService extends IncyclistService {
32
33
  static getInstance(): DevicePairingService;
33
34
  constructor(services?: Services);
34
35
  start(onStateChanged: (newState: PairingState) => void): Promise<void>;
36
+ protected loadConfiguration(): Promise<void>;
35
37
  protected initConfigHandlers(): void;
36
38
  protected removeConfigHandlers(): void;
37
39
  stop(): Promise<void>;
@@ -41,6 +43,7 @@ export declare class DevicePairingService extends IncyclistService {
41
43
  deleteDevice(capability: IncyclistCapability, udid: string, deleteAll?: boolean): Promise<void>;
42
44
  unselectDevices(capability: IncyclistCapability): Promise<void>;
43
45
  changeInterfaceSettings(name: string, settings: InterfaceSetting): Promise<void>;
46
+ protected restartPair(): Promise<void>;
44
47
  protected restart(): Promise<void>;
45
48
  protected _stop(): Promise<void>;
46
49
  protected getCapability(capability: IncyclistCapability | CapabilityData): CapabilityData;
@@ -77,6 +80,7 @@ export declare class DevicePairingService extends IncyclistService {
77
80
  private stopPairing;
78
81
  protected run(props?: PairingProps): Promise<void>;
79
82
  private startPairing;
83
+ private isReadyToPair;
80
84
  private processConnectedDevices;
81
85
  private startScanning;
82
86
  protected deregisterScanningDataHandlers(): void;
@@ -105,6 +109,7 @@ export declare class DevicePairingService extends IncyclistService {
105
109
  protected deleteCapabilityDevice(capability: IncyclistCapability, udid: string, shouldEmit?: boolean): void;
106
110
  protected isScanning(): boolean;
107
111
  protected isPairing(): boolean;
112
+ protected isPairingWaiting(): NodeJS.Timeout;
108
113
  protected _stopDeviceSelection(changed: boolean): Promise<void>;
109
114
  private numberOfSelectedCababilities;
110
115
  protected addToDeletedList(capability: IncyclistCapability | CapabilityData, udid: string): void;
@@ -69,16 +69,7 @@ class DevicePairingService extends service_2.IncyclistService {
69
69
  this.emitStateChange(this.state);
70
70
  return;
71
71
  }
72
- yield this.waitForInit();
73
- const { capabilities, interfaces } = this.configuration.load();
74
- const state = Object.assign({}, this.state);
75
- delete state.adapters;
76
- this.state.capabilities = this.mappedCapabilities(capabilities);
77
- this.state.interfaces = this.access.enrichWithAccessState(interfaces);
78
- this.state.canStartRide = this.configuration.canStartRide();
79
- this.state.stopRequested = false;
80
- this.state.stopped = false;
81
- this.logCapabilities();
72
+ yield this.loadConfiguration();
82
73
  this.state.interfaces.forEach(i => {
83
74
  if (!this.isInterfaceEnabled(i.name))
84
75
  this.unselectOnInterface(i.name);
@@ -93,6 +84,20 @@ class DevicePairingService extends service_2.IncyclistService {
93
84
  }
94
85
  });
95
86
  }
87
+ loadConfiguration() {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ yield this.waitForInit();
90
+ const { capabilities, interfaces } = this.configuration.load();
91
+ const state = Object.assign({}, this.state);
92
+ delete state.adapters;
93
+ this.state.capabilities = this.mappedCapabilities(capabilities);
94
+ this.state.interfaces = this.access.enrichWithAccessState(interfaces);
95
+ this.state.canStartRide = this.configuration.canStartRide();
96
+ this.state.stopRequested = false;
97
+ this.state.stopped = false;
98
+ this.logCapabilities();
99
+ });
100
+ }
96
101
  initConfigHandlers() {
97
102
  this.configuration.on('interface-changed', this.onInterfaceConfigChangedHandler);
98
103
  this.configuration.on('capability-changed', this.onConfigurationUpdateHandler);
@@ -242,6 +247,17 @@ class DevicePairingService extends service_2.IncyclistService {
242
247
  }
243
248
  });
244
249
  }
250
+ restartPair() {
251
+ var _a, _b;
252
+ return __awaiter(this, void 0, void 0, function* () {
253
+ if (!this.isPairing())
254
+ return;
255
+ if ((_a = this.state.check) === null || _a === void 0 ? void 0 : _a.to)
256
+ clearTimeout((_b = this.state.check) === null || _b === void 0 ? void 0 : _b.to);
257
+ delete this.state.check;
258
+ this.run();
259
+ });
260
+ }
245
261
  restart() {
246
262
  return __awaiter(this, void 0, void 0, function* () {
247
263
  const wasActive = this.isPairing() || this.isScanning();
@@ -437,6 +453,7 @@ class DevicePairingService extends service_2.IncyclistService {
437
453
  this.logEvent({ message: 'interface state changed', interface: ifName, state: ifDetails.state });
438
454
  try {
439
455
  let restartScan = false;
456
+ let restartPair = false;
440
457
  if (interfacesNew) {
441
458
  const getData = (i) => ({ name: i.name, enabled: i.enabled, state: i.state });
442
459
  if (!prev)
@@ -449,6 +466,20 @@ class DevicePairingService extends service_2.IncyclistService {
449
466
  const changedIdx = prev.findIndex(i => i.name === ifName);
450
467
  if (ifDetails.state === 'disconnected' && current.state !== 'disconnected') {
451
468
  this.failAdaptersOnInterface(ifName);
469
+ if (this.isPairing()) {
470
+ const pairing = this.getPairingInterfaces();
471
+ if (pairing.includes(ifName)) {
472
+ try {
473
+ current.state = 'disconnected';
474
+ this.emitStateChange({ interfaces: this.state.interfaces });
475
+ yield (0, utils_1.sleep)(1000);
476
+ this.access.connect(ifName);
477
+ }
478
+ catch (err) {
479
+ this.logError(err, 'reconnect');
480
+ }
481
+ }
482
+ }
452
483
  }
453
484
  else if (ifDetails.state === 'unavailable' && current.state !== 'unavailable') {
454
485
  this.disableAdaptersOnInterface(ifName);
@@ -460,12 +491,18 @@ class DevicePairingService extends service_2.IncyclistService {
460
491
  else if (ifDetails.state === 'connected' && current.state !== 'connected' && !this.isPairing()) {
461
492
  restartScan = true;
462
493
  }
494
+ else if (ifDetails.state === 'connected' && current.state !== 'connected' && this.isPairingWaiting()) {
495
+ restartPair = true;
496
+ }
463
497
  if (changedIdx !== -1) {
464
498
  prev[changedIdx].isScanning = ifDetails.isScanning;
465
499
  prev[changedIdx].state = ifDetails.state;
466
500
  }
467
501
  if (restartScan)
468
502
  this.restart();
503
+ if (restartPair) {
504
+ this.restartPair();
505
+ }
469
506
  this.emitStateChange({ interfaces: this.state.interfaces });
470
507
  }
471
508
  }
@@ -770,16 +807,15 @@ class DevicePairingService extends service_2.IncyclistService {
770
807
  const preparing = DevicePairingService.checkCounter++;
771
808
  this.state.check = { preparing };
772
809
  this.emit('pairing-start');
773
- const requiredInterfaces = this.getPairingInterfaces();
774
- const busyRequired = this.state.interfaces
775
- .filter(i => requiredInterfaces.includes(i.name))
776
- .find(i => i.enabled && i.state !== 'connected' && i.state !== 'unavailable');
777
- const isReady = busyRequired === undefined;
810
+ const { isReady, busyRequired } = this.isReadyToPair();
778
811
  if (!isReady) {
779
- setTimeout(() => {
812
+ this.logEvent({ message: 'Pairing: waiting for interfaces', interfaces: busyRequired === null || busyRequired === void 0 ? void 0 : busyRequired.name });
813
+ this.state.check.to = setTimeout(() => {
780
814
  if ((!this.isPairing() || this.state.check.preparing === preparing) && !this.isScanning()) {
781
- delete this.state.check;
782
- this.run();
815
+ const { isReady } = this.isReadyToPair();
816
+ if (isReady) {
817
+ delete this.state.check;
818
+ }
783
819
  }
784
820
  }, 1000);
785
821
  return;
@@ -810,6 +846,14 @@ class DevicePairingService extends service_2.IncyclistService {
810
846
  }
811
847
  });
812
848
  }
849
+ isReadyToPair() {
850
+ const requiredInterfaces = this.getPairingInterfaces();
851
+ const busyRequired = this.state.interfaces
852
+ .filter(i => requiredInterfaces.includes(i.name))
853
+ .find(i => i.enabled && i.state !== 'connected' && i.state !== 'unavailable');
854
+ const isReady = busyRequired === undefined;
855
+ return { isReady, busyRequired };
856
+ }
813
857
  processConnectedDevices(adapters) {
814
858
  const started = adapters.filter(ai => ai.adapter.isStarted());
815
859
  started.forEach(ai => {
@@ -1105,6 +1149,9 @@ class DevicePairingService extends service_2.IncyclistService {
1105
1149
  var _a;
1106
1150
  return this.state.check !== undefined && ((_a = this.state) === null || _a === void 0 ? void 0 : _a.check) !== null;
1107
1151
  }
1152
+ isPairingWaiting() {
1153
+ return this.isPairing() && this.state.check.to;
1154
+ }
1108
1155
  _stopDeviceSelection(changed) {
1109
1156
  return __awaiter(this, void 0, void 0, function* () {
1110
1157
  this.deviceSelectState = null;
@@ -0,0 +1,5 @@
1
+ import { XMLParser } from './xml';
2
+ export declare class IncyclistXMLParser extends XMLParser {
3
+ static SCHEME: string;
4
+ protected getGPXFileContent(url: any): Promise<any>;
5
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.IncyclistXMLParser = void 0;
16
+ const axios_1 = __importDefault(require("axios"));
17
+ const xml_1 = require("./xml");
18
+ class IncyclistXMLParser extends xml_1.XMLParser {
19
+ getGPXFileContent(url) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ const res = yield axios_1.default.get(url);
22
+ return res.data;
23
+ });
24
+ }
25
+ }
26
+ exports.IncyclistXMLParser = IncyclistXMLParser;
27
+ IncyclistXMLParser.SCHEME = 'gpx-import';
File without changes
File without changes
@@ -17,11 +17,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.useParsers = void 0;
18
18
  __exportStar(require("./kwt"), exports);
19
19
  const factory_1 = require("./factory");
20
+ const incyclist_xml_1 = require("./incyclist-xml");
20
21
  const kwt_1 = require("./kwt");
22
+ const multixml_1 = require("./multixml");
21
23
  const useParsers = () => {
22
24
  const parsers = factory_1.ParserFactory.getInstance();
23
25
  if (!parsers.isInitialized()) {
24
- parsers.add(new kwt_1.KWTParser());
26
+ parsers.add(new multixml_1.MultipleXMLParser([kwt_1.KWTParser, incyclist_xml_1.IncyclistXMLParser]));
25
27
  parsers.setInitialized(true);
26
28
  }
27
29
  return parsers;
@@ -0,0 +1,12 @@
1
+ /// <reference types="node" />
2
+ import { FileInfo, IFileLoader } from "../../../api";
3
+ import { RouteApiDetail } from "../api/types";
4
+ import { ParseResult, Parser } from "../types";
5
+ import { XMLParser } from "./xml";
6
+ export declare class MultipleXMLParser implements Parser<string | Buffer, RouteApiDetail> {
7
+ protected parsers: any;
8
+ constructor(classes: Array<typeof XMLParser>);
9
+ import(file: FileInfo, data: string | Buffer, loader?: IFileLoader): Promise<ParseResult<RouteApiDetail>>;
10
+ supportsExtension(extension: string): boolean;
11
+ supportsContent(): boolean;
12
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MultipleXMLParser = void 0;
13
+ const xml_1 = require("../utils/xml");
14
+ class MultipleXMLParser {
15
+ constructor(classes) {
16
+ this.parsers = [];
17
+ classes.forEach(C => {
18
+ const parser = new C();
19
+ this.parsers.push(parser);
20
+ });
21
+ }
22
+ import(file, data, loader) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ const str = Buffer.isBuffer(data) ? data.toString() : data;
25
+ const xmlJson = yield (0, xml_1.parseXml)(str);
26
+ const parser = this.parsers.find(p => p.supportsContent(xmlJson));
27
+ return yield parser.import(file, xmlJson, loader);
28
+ });
29
+ }
30
+ supportsExtension(extension) {
31
+ return (extension === null || extension === void 0 ? void 0 : extension.toLowerCase()) === 'xml';
32
+ }
33
+ supportsContent() {
34
+ return true;
35
+ }
36
+ }
37
+ exports.MultipleXMLParser = MultipleXMLParser;
@@ -4,6 +4,4 @@ export declare const addVideoSpeed: (points: Array<RoutePoint>, video: any) => A
4
4
  export declare const getReferencedFileInfo: (info: FileInfo, referenced: {
5
5
  file?: string;
6
6
  url?: string;
7
- }, scheme?: string) => {
8
- url: string;
9
- };
7
+ }, scheme?: string) => string;
@@ -35,23 +35,23 @@ const addVideoSpeed = (points, video) => {
35
35
  exports.addVideoSpeed = addVideoSpeed;
36
36
  const getReferencedFileInfo = (info, referenced, scheme = 'file') => {
37
37
  if (info.type !== 'url')
38
- return;
38
+ return info.url;
39
39
  const target = {};
40
40
  let fileName = referenced.file;
41
41
  if (referenced.url) {
42
- return { url: referenced.url };
42
+ return referenced.url;
43
43
  }
44
44
  if (fileName) {
45
45
  const inputUrl = info.url;
46
46
  const regex = /(\\|\/)/g;
47
47
  if (fileName.startsWith('http://') || fileName.startsWith('https://')) {
48
- return { url: fileName };
48
+ return fileName;
49
49
  }
50
50
  else if (fileName.search(regex) === -1) {
51
51
  if (inputUrl.startsWith('incyclist:') || inputUrl.startsWith('file:')) {
52
52
  const parts = inputUrl.split('://');
53
53
  const targetPath = parts[1].replace(info.name, fileName);
54
- return { url: `${scheme}://${targetPath}` };
54
+ return `${scheme}://${targetPath}`;
55
55
  }
56
56
  }
57
57
  else {
@@ -61,8 +61,9 @@ const getReferencedFileInfo = (info, referenced, scheme = 'file') => {
61
61
  target.url = `${scheme}:///${fileName}`;
62
62
  }
63
63
  else {
64
- referenced.url = `${scheme}:///${fileName}`;
64
+ target.url = `${scheme}:///${fileName}`;
65
65
  }
66
+ return target.url;
66
67
  }
67
68
  }
68
69
  };
@@ -1,10 +1,25 @@
1
1
  import { XmlJSON } from '../utils/xml';
2
2
  import { RouteApiDetail } from '../api/types';
3
- import { ParseResult, Parser } from '../types';
4
- export declare abstract class XMLParser implements Parser<XmlJSON, RouteApiDetail> {
5
- import(xml: XmlJSON): Promise<ParseResult<RouteApiDetail>>;
3
+ import { ParseResult, Parser, RouteInfo } from '../types';
4
+ import { FileInfo, IFileLoader, JSONObject } from '../../../api';
5
+ export type XmlParserContext = {
6
+ fileInfo: FileInfo;
7
+ data: JSONObject;
8
+ route?: RouteApiDetail;
9
+ };
10
+ export declare class XMLParser implements Parser<XmlJSON, RouteApiDetail> {
11
+ protected loader: IFileLoader;
12
+ import(file: FileInfo, xml: XmlJSON, loader?: IFileLoader): Promise<ParseResult<RouteApiDetail>>;
6
13
  getSupportedSheme(): string;
7
14
  supportsExtension(extension: string): boolean;
8
15
  supportsContent(xmljson: XmlJSON): boolean;
9
- protected parse(xmljson: XmlJSON): ParseResult<RouteApiDetail>;
16
+ protected loadDescription(context: XmlParserContext): Promise<void>;
17
+ protected parse(file: FileInfo, xmljson: XmlJSON): Promise<ParseResult<RouteApiDetail>>;
18
+ protected buildInfo(context: XmlParserContext): Promise<RouteInfo>;
19
+ parseVideo(context: XmlParserContext): Promise<void>;
20
+ loadElevationFromAltitudes(context: XmlParserContext, tagName?: string): Promise<void>;
21
+ loadElevationFromPositions(context: XmlParserContext, tags?: {
22
+ altitudes?: string;
23
+ positions?: string;
24
+ }): Promise<void>;
10
25
  }
@@ -13,12 +13,14 @@ exports.XMLParser = void 0;
13
13
  const gd_eventlog_1 = require("gd-eventlog");
14
14
  const xml_1 = require("../utils/xml");
15
15
  const route_1 = require("../utils/route");
16
+ const utils_1 = require("./utils");
16
17
  let _logger;
17
18
  class XMLParser {
18
- import(xml) {
19
+ import(file, xml, loader) {
19
20
  return __awaiter(this, void 0, void 0, function* () {
20
21
  xml.expectScheme(this.getSupportedSheme());
21
- return this.parse(xml);
22
+ this.loader = loader;
23
+ return yield this.parse(file, xml);
22
24
  });
23
25
  }
24
26
  getSupportedSheme() {
@@ -30,174 +32,202 @@ class XMLParser {
30
32
  }
31
33
  supportsContent(xmljson) {
32
34
  const json = xmljson.json;
33
- return json.kwt !== undefined && json.kwt !== null;
35
+ const scheme = this.getSupportedSheme();
36
+ return json[scheme] !== undefined && json[scheme] !== null;
34
37
  }
35
- parse(xmljson) {
36
- const data = xmljson.json;
37
- const route = {
38
- title: data.name,
39
- localizedTitle: data.title || data.name,
40
- country: data.country,
41
- id: data.id,
42
- category: 'personal',
43
- previewUrl: data.previewURL,
44
- distance: 0,
45
- elevation: 0,
46
- points: [],
47
- description: data.description1
48
- };
49
- const positions = data.positions;
50
- if ((positions === null || positions === void 0 ? void 0 : positions.length) > 0) {
51
- loadElevationFromPositions(data, route);
52
- }
53
- else {
54
- loadElevationFromAltitudes(data, route);
55
- }
56
- parseVideo(data, route);
57
- const res = {
58
- data: buildInfo(route),
59
- details: route
60
- };
61
- return res;
38
+ loadDescription(context) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ const data = context.data;
41
+ context.route = {
42
+ title: data['name'],
43
+ localizedTitle: data['title'] || data['name'],
44
+ country: data['country'],
45
+ id: data['id'],
46
+ previewUrl: data['previewURL'],
47
+ distance: 0,
48
+ elevation: 0,
49
+ points: [],
50
+ description: data['description1']
51
+ };
52
+ if (typeof context.route.localizedTitle === 'string') {
53
+ const lt = context.route.localizedTitle;
54
+ context.route.localizedTitle = { en: lt };
55
+ }
56
+ });
62
57
  }
63
- }
64
- exports.XMLParser = XMLParser;
65
- const buildInfo = (route) => {
66
- var _a, _b;
67
- const info = {
68
- id: route.id,
69
- title: route.title,
70
- localizedTitle: route.localizedTitle,
71
- category: route.category,
72
- country: route.country,
73
- distance: route.distance,
74
- elevation: route.elevation,
75
- points: route.points,
76
- segments: (_a = route.video) === null || _a === void 0 ? void 0 : _a.selectableSegments,
77
- requiresDownload: false,
78
- hasGpx: ((_b = route.points) === null || _b === void 0 ? void 0 : _b.length) > 0,
79
- hasVideo: true,
80
- isDemo: false,
81
- isLocal: true,
82
- isLoop: (0, route_1.checkIsLoop)(route.points),
83
- videoFormat: route.video.format,
84
- videoUrl: getVideoUrl(route),
85
- previewUrl: getPreviewUrl(route)
86
- };
87
- return info;
88
- };
89
- const getVideoUrl = (route) => {
90
- return 'url';
91
- };
92
- const getPreviewUrl = (route) => {
93
- return 'url';
94
- };
95
- const parseVideo = (json, route) => {
96
- route.video = {
97
- file: json['video-file-path'],
98
- url: undefined,
99
- framerate: parseFloat(json['framerate']),
100
- next: json['next-video'],
101
- mappings: [],
102
- format: undefined,
103
- selectableSegments: json['segments'],
104
- infoTexts: json['informations'],
105
- };
106
- console.log(route.video);
107
- const fileParts = route.video.file.split('.');
108
- const extension = fileParts[fileParts.length - 1];
109
- route.video.format = extension.toLowerCase();
110
- const mappings = json['mappings'];
111
- if (mappings) {
112
- let prev;
113
- let prevTime = 0;
114
- const startFrame = parseInt(json['start-frame'] || 0);
115
- const endFrame = json['end-frame'] ? parseInt(json['end-frame']) : undefined;
116
- try {
117
- const getDistance = (mapping, prevMapping, idx) => {
118
- const prevDist = prevMapping.distance || 0;
119
- const prevFrame = prevMapping.frame || startFrame;
120
- if (mapping.distance !== undefined)
121
- return mapping.distance;
122
- if (prev.dpf !== undefined && mapping.frame !== undefined)
123
- return prev.dpf * (mapping.frame - prevFrame) + prevDist;
124
- throw new Error(`mapping #${idx || 'total'}: one of [distance], [dpf or frame] is missing: <mapping ${(0, xml_1.toXml)(mapping)}/>`);
58
+ parse(file, xmljson) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ const data = xmljson.json;
61
+ const context = { fileInfo: file, data };
62
+ yield this.loadDescription(context);
63
+ const positions = data.positions;
64
+ if ((positions === null || positions === void 0 ? void 0 : positions.length) > 0) {
65
+ yield this.loadElevationFromPositions(context);
66
+ }
67
+ else {
68
+ yield this.loadElevationFromAltitudes(context);
69
+ }
70
+ yield this.parseVideo(context);
71
+ const res = {
72
+ data: yield this.buildInfo(context),
73
+ details: context.route
74
+ };
75
+ return res;
76
+ });
77
+ }
78
+ buildInfo(context) {
79
+ var _a, _b;
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ const { fileInfo, route } = context;
82
+ const info = {
83
+ id: route.id,
84
+ title: route.title,
85
+ localizedTitle: route.localizedTitle,
86
+ category: route.category,
87
+ country: route.country,
88
+ distance: route.distance,
89
+ elevation: route.elevation,
90
+ points: route.points,
91
+ segments: (_a = route.video) === null || _a === void 0 ? void 0 : _a.selectableSegments,
92
+ requiresDownload: false,
93
+ hasGpx: ((_b = route.points) === null || _b === void 0 ? void 0 : _b.length) > 0,
94
+ hasVideo: true,
95
+ isDemo: false,
96
+ isLocal: true,
97
+ isLoop: (0, route_1.checkIsLoop)(route.points),
98
+ videoFormat: route.video.format,
99
+ videoUrl: getVideoUrl(fileInfo, route),
100
+ previewUrl: getPreviewUrl(fileInfo, route)
101
+ };
102
+ return info;
103
+ });
104
+ }
105
+ parseVideo(context) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ const { data, route } = context;
108
+ route.video = {
109
+ file: data['video-file-path'],
110
+ url: undefined,
111
+ framerate: parseFloat(data['framerate']),
112
+ next: data['next-video'],
113
+ mappings: [],
114
+ format: undefined,
115
+ selectableSegments: data['segments'],
116
+ infoTexts: data['informations'],
125
117
  };
126
- mappings.forEach((mapping, idx) => {
127
- if (idx !== 0) {
128
- mapping.distance = (mapping.distance !== undefined && mapping.distance !== null) ? parseInt(mapping.distance) : undefined;
129
- mapping.dpf = (mapping.dpf !== undefined && mapping.dpf !== null) ? parseFloat(mapping.dpf) : undefined;
130
- mapping.frame = (mapping.frame !== undefined && mapping.frame !== null) ? parseInt(mapping.frame) : undefined;
131
- const distance = getDistance(mapping, prev, idx);
132
- route.distance = mapping.distance = distance;
133
- const frames = mapping.frame - (prev.frame || startFrame);
134
- const videoSpeed = (prev.distance === undefined && mapping.dpf !== undefined) ? 3.6 * mapping.dpf * route.video.framerate : 3.6 * (distance - prev.distance) / frames * route.video.framerate;
135
- const time = prevTime + frames / route.video.framerate;
136
- route.video.mappings.push(Object.assign({ videoSpeed, time: prevTime }, prev));
137
- prev = mapping;
138
- prevTime = time;
139
- if (idx === mappings.length - 1) {
140
- route.video.mappings.push(Object.assign({ videoSpeed, time }, mapping));
118
+ console.log(route.video);
119
+ const fileParts = route.video.file.split('.');
120
+ const extension = fileParts[fileParts.length - 1];
121
+ route.video.format = extension.toLowerCase();
122
+ const mappings = data['mappings'];
123
+ if (mappings) {
124
+ let prev;
125
+ let prevTime = 0;
126
+ const startFrame = parseInt(data['start-frame'] || 0);
127
+ const endFrame = data['end-frame'] ? parseInt(data['end-frame']) : undefined;
128
+ try {
129
+ const getDistance = (mapping, prevMapping, idx) => {
130
+ const prevDist = prevMapping.distance || 0;
131
+ const prevFrame = prevMapping.frame || startFrame;
132
+ if (mapping.distance !== undefined)
133
+ return mapping.distance;
134
+ if (prev.dpf !== undefined && mapping.frame !== undefined)
135
+ return prev.dpf * (mapping.frame - prevFrame) + prevDist;
136
+ throw new Error(`mapping #${idx || 'total'}: one of [distance], [dpf or frame] is missing: <mapping ${(0, xml_1.toXml)(mapping)}/>`);
137
+ };
138
+ mappings.forEach((mapping, idx) => {
139
+ if (idx !== 0) {
140
+ mapping.distance = (mapping.distance !== undefined && mapping.distance !== null) ? parseInt(mapping.distance) : undefined;
141
+ mapping.dpf = (mapping.dpf !== undefined && mapping.dpf !== null) ? parseFloat(mapping.dpf) : undefined;
142
+ mapping.frame = (mapping.frame !== undefined && mapping.frame !== null) ? parseInt(mapping.frame) : undefined;
143
+ const distance = getDistance(mapping, prev, idx);
144
+ route.distance = mapping.distance = distance;
145
+ const frames = mapping.frame - (prev.frame || startFrame);
146
+ const videoSpeed = (prev.distance === undefined && mapping.dpf !== undefined) ? 3.6 * mapping.dpf * route.video.framerate : 3.6 * (distance - prev.distance) / frames * route.video.framerate;
147
+ const time = prevTime + frames / route.video.framerate;
148
+ route.video.mappings.push(Object.assign({ videoSpeed, time: prevTime }, prev));
149
+ prev = mapping;
150
+ prevTime = time;
151
+ if (idx === mappings.length - 1) {
152
+ route.video.mappings.push(Object.assign({ videoSpeed, time }, mapping));
153
+ }
154
+ }
155
+ else {
156
+ mapping.distance = (mapping.distance !== undefined && mapping.distance !== null) ? parseInt(mapping.distance) : undefined;
157
+ mapping.dpf = (mapping.dpf !== undefined && mapping.dpf !== null) ? parseFloat(mapping.dpf) : undefined;
158
+ mapping.frame = (mapping.frame !== undefined && mapping.frame !== null) ? parseInt(mapping.frame) : undefined;
159
+ prev = mapping;
160
+ prevTime = 0;
161
+ }
162
+ });
163
+ if (endFrame && prev.dpf !== undefined) {
164
+ const mapping = { frame: endFrame, dpf: prev.dpf };
165
+ route.distance = getDistance(mapping, prev);
141
166
  }
142
167
  }
143
- else {
144
- mapping.distance = (mapping.distance !== undefined && mapping.distance !== null) ? parseInt(mapping.distance) : undefined;
145
- mapping.dpf = (mapping.dpf !== undefined && mapping.dpf !== null) ? parseFloat(mapping.dpf) : undefined;
146
- mapping.frame = (mapping.frame !== undefined && mapping.frame !== null) ? parseInt(mapping.frame) : undefined;
147
- prev = mapping;
148
- prevTime = 0;
168
+ catch (err) {
169
+ if (!_logger)
170
+ _logger = new gd_eventlog_1.EventLogger('XmlParser');
171
+ _logger.logEvent({ message: 'xml details', error: err.message, mappings });
172
+ throw new Error('Could not parse XML File');
149
173
  }
150
- });
151
- if (endFrame && prev.dpf !== undefined) {
152
- const mapping = { frame: endFrame, dpf: prev.dpf };
153
- route.distance = getDistance(mapping, prev);
154
174
  }
155
- }
156
- catch (err) {
157
- if (!_logger)
158
- _logger = new gd_eventlog_1.EventLogger('XmlParser');
159
- _logger.logEvent({ message: 'xml details', error: err.message, mappings });
160
- throw new Error('Could not parse XML File');
161
- }
175
+ });
162
176
  }
163
- };
164
- const loadElevationFromAltitudes = (json, route) => {
165
- const altitudes = json['altitudes'];
166
- if (!altitudes)
167
- return;
168
- route.points = altitudes.map(a => ({
169
- routeDistance: Number(a.distance),
170
- elevation: Number(a.height),
171
- }));
172
- const points = route.points;
173
- if ((points === null || points === void 0 ? void 0 : points.length) > 0) {
174
- route.distance = points[points.length - 1].routeDistance;
177
+ loadElevationFromAltitudes(context, tagName = 'altitudes') {
178
+ return __awaiter(this, void 0, void 0, function* () {
179
+ const { data, route } = context;
180
+ const altitudes = data[tagName];
181
+ if (!altitudes)
182
+ return;
183
+ route.points = altitudes.map(a => ({
184
+ routeDistance: Number(a.distance),
185
+ elevation: Number(a.height),
186
+ }));
187
+ const points = route.points;
188
+ if ((points === null || points === void 0 ? void 0 : points.length) > 0) {
189
+ route.distance = points[points.length - 1].routeDistance;
190
+ }
191
+ (0, route_1.updateSlopes)(points);
192
+ });
193
+ }
194
+ loadElevationFromPositions(context, tags) {
195
+ return __awaiter(this, void 0, void 0, function* () {
196
+ const { data, route } = context;
197
+ const altitudes = data[(tags === null || tags === void 0 ? void 0 : tags.altitudes) || 'altitudes'];
198
+ const positions = data[(tags === null || tags === void 0 ? void 0 : tags.positions) || 'positions'];
199
+ if (!positions)
200
+ return;
201
+ let prevAltitude = undefined;
202
+ let prevDistance = 0;
203
+ route.elevation = 0;
204
+ const points = [];
205
+ positions.forEach((pos, i) => {
206
+ const altitude = getAltitude(altitudes, positions, i, prevAltitude);
207
+ const elevationGain = altitude - prevAltitude;
208
+ const pi = createPoint(pos, altitude, prevDistance);
209
+ points.push(pi.point);
210
+ route.distance = pi.point.routeDistance;
211
+ if (elevationGain > 0)
212
+ route.elevation += elevationGain;
213
+ prevDistance = pi.prevDistance;
214
+ prevAltitude = altitude;
215
+ });
216
+ route.points = points;
217
+ route.distance = prevDistance;
218
+ (0, route_1.updateSlopes)(route.points);
219
+ });
175
220
  }
176
- (0, route_1.updateSlopes)(points);
221
+ }
222
+ exports.XMLParser = XMLParser;
223
+ const getVideoUrl = (info, route) => {
224
+ const { file, url } = (route === null || route === void 0 ? void 0 : route.video) || {};
225
+ return (0, utils_1.getReferencedFileInfo)(info, { file, url }, 'video');
177
226
  };
178
- const loadElevationFromPositions = (json, route) => {
179
- const altitudes = json['altitudes'];
180
- const positions = json['positions'];
181
- if (!positions)
182
- return;
183
- let prevAltitude = undefined;
184
- let prevDistance = 0;
185
- route.elevation = 0;
186
- const points = [];
187
- positions.forEach((pos, i) => {
188
- const altitude = getAltitude(altitudes, positions, i, prevAltitude);
189
- const elevationGain = altitude - prevAltitude;
190
- const pi = createPoint(pos, altitude, prevDistance);
191
- points.push(pi.point);
192
- route.distance = pi.point.routeDistance;
193
- if (elevationGain > 0)
194
- route.elevation += elevationGain;
195
- prevDistance = pi.prevDistance;
196
- prevAltitude = altitude;
197
- });
198
- route.points = points;
199
- route.distance = prevDistance;
200
- (0, route_1.updateSlopes)(route.points);
227
+ const getPreviewUrl = (info, route) => {
228
+ const url = route === null || route === void 0 ? void 0 : route.previewUrl;
229
+ const file = route === null || route === void 0 ? void 0 : route.previewUrlLocal;
230
+ return (0, utils_1.getReferencedFileInfo)(info, { file, url }, 'file');
201
231
  };
202
232
  function createPoint(pos, altitude, prevDistance) {
203
233
  const point = {
@@ -1,3 +1,4 @@
1
+ import { FileInfo, IFileLoader } from "../../../api";
1
2
  export type RouteType = 'gpx' | 'video';
2
3
  export type RouteCategory = 'Free' | 'Demo' | 'personal';
3
4
  export type RouteState = 'prepared' | 'loading' | 'loaded' | 'error';
@@ -79,7 +80,7 @@ export interface ParseResult<T extends RouteBase> {
79
80
  details: T;
80
81
  }
81
82
  export interface Parser<In, Out extends RouteBase> {
82
- import(data: In): Promise<ParseResult<Out>>;
83
+ import(file: FileInfo, data: In, loader?: IFileLoader): Promise<ParseResult<Out>>;
83
84
  supportsExtension(extension: string): boolean;
84
85
  supportsContent(data: In): boolean;
85
86
  }
@@ -72,7 +72,7 @@ export declare class RouteListService extends IncyclistService {
72
72
  protected initRouteLists(): void;
73
73
  protected getPageState(pageId: string, lang?: string): RouteListData;
74
74
  protected registerPage(pageId: string, props: RouteListStartProps): Page;
75
- protected sort(routes: RouteInfo[] | Route[]): void;
75
+ protected sort(list: List, routes: RouteInfo[] | Route[]): void;
76
76
  protected getRouteListDataEntry(list: List, entry: Array<Route>, language: string, listHeader: string): {
77
77
  list: List;
78
78
  listHeader: string;
@@ -96,6 +96,7 @@ export declare class RouteListService extends IncyclistService {
96
96
  protected addDescriptionsFromDB(descriptions: RoutesDB): void;
97
97
  protected addDescriptionsFromRepo(descriptions: Array<RouteApiDescription>, repo?: JsonRepository): void;
98
98
  protected addDescriptionsFromServer(descriptions: Array<RouteApiDescription>): void;
99
+ protected addDescriptionFromFile(data: RouteInfo, details: RouteApiDetail): void;
99
100
  protected updateRouteTitle(data: RouteInfo, route: {
100
101
  descr?: RouteApiDescription;
101
102
  }): void;
@@ -216,30 +216,23 @@ class RouteListService extends service_1.IncyclistService {
216
216
  const parsers = (0, parsers_1.useParsers)();
217
217
  files.forEach((file) => __awaiter(this, void 0, void 0, function* () {
218
218
  try {
219
- const { data, error } = yield this.loader.open(file);
219
+ const { data: content, error } = yield this.loader.open(file);
220
220
  if (error) {
221
221
  return;
222
222
  }
223
- console.log('~~~ DATA', data);
224
- const parser = parsers.findMatching(file.ext, data);
225
- const route = parser.import(data);
226
- console.log('~~~ ROUTE', route);
223
+ console.log('~~~ DATA', content);
224
+ const parser = parsers.findMatching(file.ext, content);
225
+ const res = yield parser.import(file, content);
226
+ const data = res.data;
227
+ data.state = 'loaded';
228
+ const details = res.data;
229
+ this.addDescriptionFromFile(data, details);
230
+ console.log('~~~ ROUTES', this.getRouteList('myRoutes'));
227
231
  }
228
232
  catch (err) {
229
233
  console.log(err);
230
234
  }
231
235
  }));
232
- const route = {
233
- id: Date.now().toString(),
234
- data: {
235
- type: 'route',
236
- id: Date.now().toString(),
237
- state: 'prepared',
238
- title: 'Test'
239
- }
240
- };
241
- this.addRouteToList('myRoutes', route);
242
- this.emitPageUpdate();
243
236
  }
244
237
  setCardUpdateHandler(pageId, r, list, idx, onRouteStateChanged, onCarouselStateChanged) {
245
238
  const route = this.getRouteFromStartProps(r);
@@ -362,7 +355,6 @@ class RouteListService extends service_1.IncyclistService {
362
355
  const promises = [];
363
356
  const rle = this.lists.find(l => l.list === list);
364
357
  if (((_a = rle.routes) === null || _a === void 0 ? void 0 : _a.length) > 0) {
365
- this.sort(rle.routes);
366
358
  let cntApi = 0;
367
359
  rle.routes.forEach((r) => {
368
360
  if (!r.data.isLocal) {
@@ -491,27 +483,36 @@ class RouteListService extends service_1.IncyclistService {
491
483
  this.state.pages.push(page);
492
484
  return page;
493
485
  }
494
- sort(routes) {
486
+ sort(list, routes) {
495
487
  const isRouteArray = (a) => a[0].data !== undefined;
496
488
  let val = (route) => route;
497
489
  if (isRouteArray(routes)) {
498
- val = (route) => route.data;
490
+ val = (route) => (Object.assign(Object.assign({}, route.data), { type: route.type, created: route.created }));
499
491
  }
500
492
  const score = (route) => {
493
+ if (route.type === 'import')
494
+ return 1000;
495
+ if (route.type === 'free-ride')
496
+ return 1.2;
501
497
  let val = 0;
498
+ if (route.created) {
499
+ const tDelta = (Date.now() - route.created) / 1000;
500
+ const DAY = 60 * 60 * 24;
501
+ val += Math.min((1 - (DAY - tDelta) / DAY), 1);
502
+ }
502
503
  if (route.hasVideo)
503
- val += 1000;
504
+ val += 1.000;
504
505
  if (route.hasGpx)
505
- val += 100;
506
+ val += 0.100;
506
507
  if (route.isDemo)
507
- val -= 150;
508
+ val -= 0.150;
508
509
  return val;
509
510
  };
510
511
  routes.sort((a, b) => score(val(b)) - score(val(a)));
511
512
  }
512
513
  getRouteListDataEntry(list, entry, language, listHeader) {
513
514
  const routes = entry.map(r => (0, localization_1.getLocalizedData)(r.data, language));
514
- this.sort(routes);
515
+ this.sort(list, routes);
515
516
  return { list, listHeader, routes };
516
517
  }
517
518
  getPage(pageId, byIndex) {
@@ -702,6 +703,25 @@ class RouteListService extends service_1.IncyclistService {
702
703
  }
703
704
  });
704
705
  }
706
+ addDescriptionFromFile(data, details) {
707
+ const existing = this.getRoute(data.id);
708
+ if (existing) {
709
+ return;
710
+ }
711
+ const list = 'myRoutes';
712
+ const local = this.getRouteDescription(list, data.id);
713
+ if (local) {
714
+ this.mergeDescription(local, data);
715
+ }
716
+ else {
717
+ data.state = 'loaded';
718
+ data.isLocal = true;
719
+ const item = { id: data.id, data, details, created: Date.now() };
720
+ this.addRouteToList(list, item);
721
+ this.routeDescriptions[data.id] = Object.assign(Object.assign({}, data), { list });
722
+ this.emitPageUpdate();
723
+ }
724
+ }
705
725
  updateRouteTitle(data, route) {
706
726
  const { descr } = route;
707
727
  if (descr && !data.title) {
@@ -118,6 +118,7 @@ export type Card<T> = {
118
118
  id: string;
119
119
  data: T;
120
120
  details?: RouteApiDetail;
121
+ created?: number;
121
122
  startSettings?: RouteStartSettings;
122
123
  startState?: RouteStartState;
123
124
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.0.62",
3
+ "version": "1.0.63",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.26"
6
6
  },
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "axios": "^1.6.1",
42
- "incyclist-devices": "^2.1.26",
42
+ "incyclist-devices": "file:../devices",
43
43
  "uuid": "^9.0.0",
44
44
  "xml2js": "^0.6.2"
45
45
  }