incyclist-services 1.7.52 → 1.7.53

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.
@@ -572,6 +572,8 @@ let DeviceRideService = (() => {
572
572
  if (startType === 'start') {
573
573
  this.initForStart(ai, startProps, route, startPos, realityFactor, rideMode);
574
574
  }
575
+ ai.adapter.resumeLogging();
576
+ this.resumeLogging();
575
577
  const sType = (ai.isControl) ? 'bike' : 'sensor';
576
578
  const logProps = {};
577
579
  logProps[sType] = ai.adapter.getUniqueName();
@@ -44,8 +44,10 @@ const types_1 = require("../../base/types");
44
44
  const parsers_1 = require("../base/parsers");
45
45
  const service_2 = require("../list/service");
46
46
  const utils_1 = require("../../utils");
47
- const VIDEO_EXTENSIONS = new Set(['mp4', 'mov', 'mkv', 'm4v', 'mpg', 'mpeg', 'wmv', 'avi']);
48
- const IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']);
47
+ const db_1 = require("../list/loaders/db");
48
+ const route_1 = require("../base/model/route");
49
+ const sleep_1 = require("../../utils/sleep");
50
+ const i18n_1 = require("../../i18n");
49
51
  let RouteLibraryScannerService = (() => {
50
52
  let _classDecorators = [decorators_1.Singleton];
51
53
  let _classDescriptor;
@@ -55,53 +57,220 @@ let RouteLibraryScannerService = (() => {
55
57
  let _instanceExtraInitializers = [];
56
58
  let _getRouteList_decorators;
57
59
  let _getBindings_decorators;
60
+ let _getRoutesDBLoader_decorators;
61
+ let _getParsers_decorators;
62
+ let _getUnitConverter_decorators;
58
63
  var RouteLibraryScannerService = class extends _classSuper {
59
64
  static { _classThis = this; }
60
65
  static {
61
66
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
62
67
  _getRouteList_decorators = [decorators_1.Injectable];
63
68
  _getBindings_decorators = [decorators_1.Injectable];
69
+ _getRoutesDBLoader_decorators = [decorators_1.Injectable];
70
+ _getParsers_decorators = [decorators_1.Injectable];
71
+ _getUnitConverter_decorators = [decorators_1.Injectable];
64
72
  __esDecorate(this, null, _getRouteList_decorators, { kind: "method", name: "getRouteList", static: false, private: false, access: { has: obj => "getRouteList" in obj, get: obj => obj.getRouteList }, metadata: _metadata }, null, _instanceExtraInitializers);
65
73
  __esDecorate(this, null, _getBindings_decorators, { kind: "method", name: "getBindings", static: false, private: false, access: { has: obj => "getBindings" in obj, get: obj => obj.getBindings }, metadata: _metadata }, null, _instanceExtraInitializers);
74
+ __esDecorate(this, null, _getRoutesDBLoader_decorators, { kind: "method", name: "getRoutesDBLoader", static: false, private: false, access: { has: obj => "getRoutesDBLoader" in obj, get: obj => obj.getRoutesDBLoader }, metadata: _metadata }, null, _instanceExtraInitializers);
75
+ __esDecorate(this, null, _getParsers_decorators, { kind: "method", name: "getParsers", static: false, private: false, access: { has: obj => "getParsers" in obj, get: obj => obj.getParsers }, metadata: _metadata }, null, _instanceExtraInitializers);
76
+ __esDecorate(this, null, _getUnitConverter_decorators, { kind: "method", name: "getUnitConverter", static: false, private: false, access: { has: obj => "getUnitConverter" in obj, get: obj => obj.getUnitConverter }, metadata: _metadata }, null, _instanceExtraInitializers);
66
77
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
67
78
  RouteLibraryScannerService = _classThis = _classDescriptor.value;
68
79
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
69
80
  __runInitializers(_classThis, _classExtraInitializers);
70
81
  }
82
+ isCancelled = (__runInitializers(this, _instanceExtraInitializers), false);
83
+ scanResult = [];
84
+ importProps;
71
85
  constructor() {
72
86
  super('RouteLibraryScanner');
73
- __runInitializers(this, _instanceExtraInitializers);
87
+ }
88
+ prepare() {
89
+ this.importProps = {
90
+ phase: 'landing',
91
+ routes: []
92
+ };
93
+ }
94
+ done() {
95
+ this.importProps = undefined;
96
+ }
97
+ getDisplayProps() {
98
+ return this.importProps;
99
+ }
100
+ importSingle(fileInfo) {
101
+ const observer = new types_1.Observer();
102
+ this.isCancelled = false;
103
+ this.importRoute(fileInfo, observer).catch(err => {
104
+ this.logError(err, 'importSingle', { file: fileInfo?.filename });
105
+ observer.emit('error', err.message);
106
+ });
107
+ observer.on('success', (route) => {
108
+ this.importProps.phase = 'result';
109
+ this.importProps.resultSuccess = { routeName: route.title };
110
+ });
111
+ observer.on('error', (error) => {
112
+ this.importProps.phase = 'result';
113
+ this.importProps.error = error;
114
+ });
115
+ return observer;
74
116
  }
75
117
  scan(folderInfo) {
118
+ if (!this.importProps)
119
+ this.prepare();
120
+ this.isCancelled = false;
121
+ this.scanResult = [];
122
+ this.importProps.phase = 'scanning';
76
123
  const observer = new types_1.Observer();
77
124
  this._scan(folderInfo, observer).catch(err => {
78
125
  this.logError(err, 'scan', { uri: folderInfo.uri });
79
126
  observer.emit('error', err.message);
80
127
  });
128
+ observer.on('scan-progress', (progress) => {
129
+ this.importProps.scanProgress = progress;
130
+ });
81
131
  return observer;
82
132
  }
83
- ingest(routes, treeUri) {
133
+ parse(scannedRoutes) {
84
134
  const observer = new types_1.Observer();
85
- this._ingest(routes, treeUri, observer).catch(err => {
86
- this.logError(err, 'ingest', { treeUri });
135
+ if (!this.importProps)
136
+ this.prepare();
137
+ this._parse(scannedRoutes, observer).catch(err => {
138
+ this.logError(err, 'parse');
87
139
  observer.emit('error', err.message);
88
140
  });
141
+ observer.on('parse-progress', (progress) => {
142
+ const { parsed, total } = progress;
143
+ this.importProps.parseProgress = { parsed, total };
144
+ });
145
+ observer.on('parse-result', (route) => {
146
+ this.importProps.routes.push(this.buildRouteDisplayItem(route));
147
+ });
148
+ observer.on('parse-complete', () => {
149
+ this.importProps.phase = 'selecting';
150
+ });
89
151
  return observer;
90
152
  }
153
+ ingest(routes) {
154
+ const observer = new types_1.Observer();
155
+ if (!this.importProps)
156
+ this.prepare();
157
+ const list = this.getRouteList();
158
+ list.pauseListUpdates();
159
+ this.importProps.phase = 'ingesting';
160
+ this._ingest(routes, observer)
161
+ .catch(err => {
162
+ this.logError(err, 'ingest');
163
+ observer.emit('error', err.message);
164
+ })
165
+ .finally(() => {
166
+ list.resumeListUpdates();
167
+ list.emitLists('updated', { source: 'system' });
168
+ });
169
+ observer.on('ingest-progress', (progress) => {
170
+ const { current, total, currentName } = progress;
171
+ this.importProps.ingestProgress = { current, total, currentName };
172
+ });
173
+ observer.on('ingest-complete', (status) => {
174
+ this.importProps.phase = 'complete';
175
+ const { imported, skipped, errors, failedRoutes } = status;
176
+ this.importProps.completionSummary = { imported, skipped, errors, failedRoutes };
177
+ });
178
+ return observer;
179
+ }
180
+ cancel() {
181
+ this.isCancelled = true;
182
+ this.importProps.phase = 'landing';
183
+ }
184
+ async importRoute(fileInfo, observer) {
185
+ await (0, sleep_1.sleep)(0);
186
+ observer.emit('parsing');
187
+ this.importProps.phase = 'parsing';
188
+ if (fileInfo?.ext === 'gpx') {
189
+ return this.importSingleGpxRoute(fileInfo, observer);
190
+ }
191
+ else {
192
+ return this.importSingleVideoRoute(fileInfo, observer);
193
+ }
194
+ }
195
+ async importSingleGpxRoute(fileInfo, observer) {
196
+ const list = this.getRouteList();
197
+ const db = this.getRoutesDBLoader();
198
+ try {
199
+ const { data, details } = await parsers_1.RouteParser.parse(fileInfo);
200
+ const route = new route_1.Route(data, details);
201
+ route.description.tsImported = Date.now();
202
+ await db.save(route, true);
203
+ list.addRoute(route, 'user');
204
+ observer.emit('success', route);
205
+ }
206
+ catch (err) {
207
+ observer.emit('error', err.message);
208
+ }
209
+ }
210
+ async importSingleVideoRoute(fileInfo, observer) {
211
+ const parsers = this.getParsers();
212
+ const { dir, ext, delimiter } = fileInfo;
213
+ const folderUri = dir.endsWith(delimiter ?? '/') ? dir.slice(0, -delimiter.length) : dir;
214
+ try {
215
+ if (!parsers.isPrimaryExtension(ext)) {
216
+ observer.emit('error', 'not a route control file');
217
+ return;
218
+ }
219
+ const scanObserver = new types_1.Observer();
220
+ await this.scanFolder(folderUri, folderUri, scanObserver, parsers, { scannedFolders: 0 }, { value: 0 }, false);
221
+ const files = this.scanResult;
222
+ this.scanResult = [];
223
+ scanObserver.stop();
224
+ if (files[0].scanError) {
225
+ observer.emit('error', files[0].scanError);
226
+ return;
227
+ }
228
+ else {
229
+ const file = files.find(file => file.controlFileUri.includes(fileInfo.base));
230
+ const parseObserver = this.parse([file]);
231
+ parseObserver.on('parse-result', (result) => {
232
+ parseObserver.stop();
233
+ if (result.parseError) {
234
+ observer.emit('error', result.parseError);
235
+ return;
236
+ }
237
+ const ingest = this.ingest([result]);
238
+ let ingestError;
239
+ ingest.once('ingest-error', (_, reason) => {
240
+ ingest.stop();
241
+ observer.emit('error', reason);
242
+ ingestError = reason;
243
+ });
244
+ ingest.once('ingest-complete', (summary) => {
245
+ ingest.stop();
246
+ if (!ingestError && summary.imported > 0) {
247
+ observer.emit('success', summary.importedRoutes?.[0]?.title);
248
+ }
249
+ else if (!ingestError && summary.imported === 0) {
250
+ observer.emit('error', 'not imported');
251
+ }
252
+ });
253
+ });
254
+ }
255
+ }
256
+ catch (err) {
257
+ observer.emit('error', err.message);
258
+ }
259
+ }
91
260
  async _scan(folderInfo, observer) {
92
261
  await (0, utils_1.waitNextTick)();
93
- const parsers = (0, parsers_1.useParsers)();
262
+ const parsers = this.getParsers();
94
263
  const progress = { scannedFolders: 0 };
95
264
  const discoveredCount = { value: 0 };
96
265
  await this.scanFolder(folderInfo.uri, folderInfo.displayName, observer, parsers, progress, discoveredCount);
97
266
  await this.upsertImportHistory(folderInfo, discoveredCount.value);
98
- observer.emit('scan-complete');
267
+ observer.emit('scan-complete', this.scanResult);
99
268
  }
100
- async scanFolder(uri, folderName, observer, parsers, progress, discoveredCount) {
269
+ async scanFolder(uri, folderName, observer, parsers, progress, discoveredCount, recursive = true) {
101
270
  const fs = this.getBindings().fs;
102
271
  let entries;
103
272
  try {
104
- entries = await fs.readdir(uri, { extended: true });
273
+ entries = await fs.readdir(uri, { recursive: false, extended: true });
105
274
  }
106
275
  catch (err) {
107
276
  this.logError(err, 'scanFolder', { uri });
@@ -111,137 +280,150 @@ let RouteLibraryScannerService = (() => {
111
280
  observer.emit('scan-progress', { scannedFolders: progress.scannedFolders });
112
281
  const files = entries.filter(e => !e.isDirectory);
113
282
  const dirs = entries.filter(e => e.isDirectory);
114
- const primaryFiles = files.filter(f => {
115
- const ext = this.getExtension(f.name);
283
+ const primaryFiles = files.filter((f) => {
284
+ const name = typeof (f) === 'string' ? f : f.name;
285
+ const ext = this.getExtension(name);
116
286
  return ext && parsers.isPrimaryExtension(ext);
287
+ }).map((f) => {
288
+ if (typeof f === 'string') {
289
+ return {
290
+ name: f,
291
+ isDirectory: false,
292
+ uri: this.getBindings().path.join(uri, f)
293
+ };
294
+ }
295
+ else
296
+ return f;
117
297
  });
118
298
  for (const file of primaryFiles) {
119
- const route = await this.buildDiscoveredRoute(file, files, uri, folderName, parsers);
120
- discoveredCount.value++;
121
- observer.emit('discovered', route);
299
+ if (!this.isCancelled) {
300
+ const routeAnnouncement = await this.buildDiscoveredRoute(file, files, uri, folderName, parsers);
301
+ discoveredCount.value++;
302
+ observer.emit('scan-result', routeAnnouncement);
303
+ this.scanResult.push(routeAnnouncement);
304
+ }
122
305
  }
123
306
  for (const dir of dirs) {
124
- await this.scanFolder(dir.uri, dir.name, observer, parsers, progress, discoveredCount);
307
+ if (!this.isCancelled && recursive) {
308
+ await this.scanFolder(dir.uri, dir.name, observer, parsers, progress, discoveredCount);
309
+ }
125
310
  }
126
311
  }
127
312
  async buildDiscoveredRoute(controlFile, folderFiles, folderUri, folderName, parsers) {
128
313
  const ext = this.getExtension(controlFile.name);
129
314
  const baseName = controlFile.name.slice(0, controlFile.name.length - ext.length - 1);
130
- let importable = true;
131
315
  let skipReason;
132
316
  const companionExts = this.getCompanionExts(parsers, ext);
133
317
  for (const compExt of companionExts) {
134
318
  const hasCompanion = folderFiles.some(f => this.getExtension(f.name).toLowerCase() === compExt.toLowerCase() &&
135
319
  f.name.toLowerCase().startsWith(baseName.toLowerCase()));
136
320
  if (!hasCompanion) {
137
- importable = false;
138
321
  skipReason = `Missing companion file (.${compExt})`;
139
322
  break;
140
323
  }
141
324
  }
142
- let hasVideo = false;
143
- const hasThumbnail = folderFiles.some(f => IMAGE_EXTENSIONS.has(this.getExtension(f.name).toLowerCase()));
144
- if (importable) {
145
- const videoFiles = folderFiles.filter(f => VIDEO_EXTENSIONS.has(this.getExtension(f.name).toLowerCase()));
146
- const nonAviVideo = videoFiles.find(f => this.getExtension(f.name).toLowerCase() !== 'avi');
147
- if (videoFiles.length === 0) {
148
- importable = false;
149
- skipReason = 'No video file found in folder';
150
- }
151
- else if (nonAviVideo) {
152
- hasVideo = true;
153
- }
154
- else {
155
- importable = false;
156
- skipReason = 'Only AVI video format found; AVI is not supported';
157
- }
158
- }
159
- if (importable) {
160
- try {
161
- const fs = this.getBindings().fs;
162
- const content = await fs.readFile(controlFile.uri);
163
- const text = typeof content === 'string' ? content : content?.toString?.('utf8') ?? '';
164
- if (this.containsAbsolutePath(text.slice(0, 4096))) {
165
- importable = false;
166
- skipReason = 'Route references an absolute video path';
167
- }
168
- }
169
- catch {
170
- }
171
- }
172
- const alreadyImported = await this.getRouteList()
173
- .existsBySourceUri(controlFile.uri)
174
- .catch(() => false);
175
325
  return {
176
- id: (0, uuid_1.v4)(),
177
326
  folderUri,
178
327
  folderName,
179
328
  controlFileUri: controlFile.uri,
180
329
  format: ext,
181
- hasVideo,
182
- hasThumbnail,
183
- alreadyImported,
184
- importable,
185
- skipReason
330
+ scanError: skipReason
186
331
  };
187
332
  }
188
- async _ingest(routes, treeUri, observer) {
333
+ async _parse(scannedRoutes, observer) {
334
+ const service = this.getRouteList();
335
+ const targets = scannedRoutes.filter(r => !r.scanError);
336
+ const total = targets.length;
337
+ for (let i = 0; i < targets.length; i++) {
338
+ if (this.isCancelled)
339
+ continue;
340
+ const parsed = i + 1;
341
+ const target = targets[i];
342
+ observer.emit('parse-progress', { current: parsed, parsed, total, currentFolder: target.folderName });
343
+ await this._parseTarget(target, service, observer);
344
+ }
345
+ observer.emit('parse-complete');
346
+ }
347
+ async _parseTarget(target, service, observer) {
348
+ if (service.existsBySourceUri(target.controlFileUri)) {
349
+ observer.emit('parse-result', {
350
+ alreadyImported: true,
351
+ route: service.getBySourceUri(target.controlFileUri),
352
+ folderUri: target.folderUri,
353
+ controlFileUri: target.controlFileUri,
354
+ format: target.format
355
+ });
356
+ return;
357
+ }
358
+ let result;
359
+ const file = this.buildFileInfo(target.controlFileUri, target.format);
360
+ try {
361
+ try {
362
+ result = await parsers_1.RouteParser.parse(file);
363
+ }
364
+ catch (err) {
365
+ throw new Error(`Could not parse: [${err.message}]`);
366
+ }
367
+ if (result.data.hasVideo) {
368
+ this.validateVideoUrl(result.details, target.folderUri);
369
+ }
370
+ observer.emit('parse-result', {
371
+ alreadyImported: false,
372
+ route: new route_1.Route(result.data, result.details),
373
+ folderUri: target.folderUri,
374
+ controlFileUri: target.controlFileUri,
375
+ format: target.format
376
+ });
377
+ }
378
+ catch (err) {
379
+ observer.emit('parse-result', {
380
+ alreadyImported: false,
381
+ route: result ? new route_1.Route(result.data, result.details) : undefined,
382
+ folderUri: target.folderUri,
383
+ controlFileUri: target.controlFileUri,
384
+ format: target.format,
385
+ parseError: err?.message ?? String(err)
386
+ });
387
+ }
388
+ }
389
+ validateVideoUrl(routeDetail, folderUri) {
390
+ if (this.isMobile()) {
391
+ if (routeDetail.video.format === 'avi') {
392
+ throw new Error('AVI video not supported');
393
+ }
394
+ }
395
+ if (routeDetail.video.file) {
396
+ routeDetail.video.file = this.resolveVideoUri(routeDetail.video.file, folderUri);
397
+ }
398
+ }
399
+ async _ingest(routes, observer) {
189
400
  await (0, utils_1.waitNextTick)();
190
- const importable = routes.filter(r => r.importable && !r.alreadyImported);
191
- const total = importable.length;
192
- let imported = 0;
401
+ const service = this.getRouteList();
402
+ const db = this.getRoutesDBLoader();
403
+ const target = routes.filter(r => !r.alreadyImported && !r.parseError);
404
+ const total = target.length;
193
405
  let errors = 0;
194
406
  const failedRoutes = [];
195
- for (let i = 0; i < importable.length; i++) {
196
- const route = importable[i];
197
- observer.emit('ingest-progress', { current: i + 1, total, currentName: route.folderName });
407
+ const importedRoutes = [];
408
+ for (let i = 0; i < target.length; i++) {
409
+ if (this.isCancelled)
410
+ continue;
411
+ const { route } = target[i] ?? {};
198
412
  try {
199
- await this.ingestRoute(route, treeUri);
200
- imported++;
413
+ observer.emit('ingest-progress', { current: i + 1, total, currentName: route.title });
414
+ await db.save(route, true);
415
+ service.addRoute(route, 'user');
416
+ importedRoutes.push(route);
201
417
  }
202
418
  catch (err) {
203
419
  const reason = err?.message ?? String(err);
204
420
  errors++;
205
- failedRoutes.push({ name: route.folderName, reason });
206
- observer.emit('ingest-error', { name: route.folderName, reason });
421
+ failedRoutes.push({ name: route.title, reason });
422
+ observer.emit('ingest-error', { name: route.title, reason });
207
423
  }
208
424
  }
209
- const skipped = routes.length - importable.length;
210
- observer.emit('ingest-complete', { imported, skipped, errors, failedRoutes });
211
- }
212
- async ingestRoute(route, treeUri) {
213
- const { format, controlFileUri, folderUri } = route;
214
- const fileInfo = this.buildFileInfo(controlFileUri, format);
215
- const { data } = await parsers_1.RouteParser.parse(fileInfo);
216
- let thumbnailPath;
217
- if (route.hasThumbnail) {
218
- thumbnailPath = await this.copyThumbnail(route, folderUri).catch(() => undefined);
219
- }
220
- const videoUri = data.videoUrl ? this.resolveVideoUri(data.videoUrl, folderUri) : undefined;
221
- const record = {
222
- id: data.id ?? (0, uuid_1.v4)(),
223
- name: data.title ?? route.folderName,
224
- format,
225
- thumbnailPath,
226
- videoUri,
227
- sourceTreeUri: treeUri
228
- };
229
- await this.getRouteList().addRoute(record);
230
- }
231
- async copyThumbnail(route, folderUri) {
232
- const { fs, appInfo } = this.getBindings();
233
- if (!fs || !appInfo)
234
- return undefined;
235
- const entries = await fs.readdir(folderUri, { extended: true });
236
- const thumbnailFile = entries.find(e => !e.isDirectory && IMAGE_EXTENSIONS.has(this.getExtension(e.name).toLowerCase()));
237
- if (!thumbnailFile)
238
- return undefined;
239
- const destDir = `${appInfo.getAppDir()}/thumbnails`;
240
- await fs.ensureDir(destDir);
241
- const srcContent = await fs.readFile(thumbnailFile.uri);
242
- const destPath = `${destDir}/${route.id}.${this.getExtension(thumbnailFile.name)}`;
243
- await fs.writeFile(destPath, srcContent);
244
- return destPath;
425
+ const skipped = routes.length - target.length;
426
+ observer.emit('ingest-complete', { imported: importedRoutes.length, skipped, errors, failedRoutes, importedRoutes });
245
427
  }
246
428
  resolveVideoUri(videoRef, folderUri) {
247
429
  if (videoRef.startsWith('http://') || videoRef.startsWith('https://')) {
@@ -251,7 +433,9 @@ let RouteLibraryScannerService = (() => {
251
433
  return videoRef;
252
434
  }
253
435
  if (videoRef.startsWith('/') || /^[A-Za-z]:[/\\]/.test(videoRef)) {
254
- throw new Error('Absolute video path references are not supported during ingest');
436
+ if (this.isMobile())
437
+ throw new Error('Absolute video path references are not supported during ingest');
438
+ return videoRef;
255
439
  }
256
440
  return `${folderUri}/${videoRef}`;
257
441
  }
@@ -292,6 +476,24 @@ let RouteLibraryScannerService = (() => {
292
476
  delimiter: '/'
293
477
  };
294
478
  }
479
+ buildRouteDisplayItem(parsed) {
480
+ const { route, alreadyImported, parseError, format } = parsed;
481
+ const descr = route?.description ?? {};
482
+ const [C, U] = this.getUnitConversionShortcuts();
483
+ const distance = descr.distance === undefined ? undefined : {
484
+ value: C(descr.distance, 'distance', { digits: 1 }),
485
+ unit: U('distance')
486
+ };
487
+ return {
488
+ id: route.description.id,
489
+ distance,
490
+ label: route.title,
491
+ alreadyImported,
492
+ importable: parseError != null,
493
+ format,
494
+ errorReason: parseError
495
+ };
496
+ }
295
497
  getCompanionExts(parsers, primaryExt) {
296
498
  try {
297
499
  const matching = parsers.suppertsExtension(primaryExt);
@@ -313,12 +515,27 @@ let RouteLibraryScannerService = (() => {
313
515
  return true;
314
516
  return false;
315
517
  }
518
+ isMobile() {
519
+ return this.getBindings()?.appInfo?.getChannel() === 'mobile';
520
+ }
316
521
  getRouteList() {
317
522
  return (0, service_2.useRouteList)();
318
523
  }
319
524
  getBindings() {
320
525
  return (0, api_1.getBindings)();
321
526
  }
527
+ getRoutesDBLoader() {
528
+ return (0, db_1.useRoutesDbLoader)();
529
+ }
530
+ getParsers() {
531
+ return (0, parsers_1.useParsers)();
532
+ }
533
+ getUnitConversionShortcuts() {
534
+ return this.getUnitConverter().getUnitConversionShortcuts();
535
+ }
536
+ getUnitConverter() {
537
+ return (0, i18n_1.useUnitConverter)();
538
+ }
322
539
  };
323
540
  return RouteLibraryScannerService = _classThis;
324
541
  })();
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.RoutesDbLoader = void 0;
40
+ exports.useRoutesDbLoader = exports.RoutesDbLoader = void 0;
41
41
  const api_1 = require("../../../api");
42
42
  const types_1 = require("../../../base/types");
43
43
  const observer_1 = require("../../../base/types/observer");
@@ -339,3 +339,5 @@ let RoutesDbLoader = (() => {
339
339
  return RoutesDbLoader = _classThis;
340
340
  })();
341
341
  exports.RoutesDbLoader = RoutesDbLoader;
342
+ const useRoutesDbLoader = () => new RoutesDbLoader();
343
+ exports.useRoutesDbLoader = useRoutesDbLoader;
@@ -126,6 +126,7 @@ let RouteListService = (() => {
126
126
  syncInfo;
127
127
  currentView;
128
128
  stats;
129
+ isListUpdatePaused = false;
129
130
  constructor() {
130
131
  super('RouteList');
131
132
  this.myRoutes = new myroutes_1.MyRoutes('myRoutes', 'My Routes');
@@ -709,7 +710,15 @@ let RouteListService = (() => {
709
710
  this.logError(err, 'import', info);
710
711
  }
711
712
  }
713
+ pauseListUpdates() {
714
+ this.isListUpdatePaused = true;
715
+ }
716
+ resumeListUpdates() {
717
+ this.isListUpdatePaused = false;
718
+ }
712
719
  emitLists(event, props) {
720
+ if (this.isListUpdatePaused)
721
+ return;
713
722
  try {
714
723
  const { log, source = 'user' } = props ?? {};
715
724
  if (this.currentView === 'grid' || this.currentView === 'list') {
@@ -791,6 +800,25 @@ let RouteListService = (() => {
791
800
  return [];
792
801
  }
793
802
  }
803
+ existsBySourceUri(uri) {
804
+ try {
805
+ return this.routes.some(r => r.description?.sourceTreeUri === uri ||
806
+ r.description?.videoUrl === uri);
807
+ }
808
+ catch (err) {
809
+ this.logError(err, 'existsBySourceUri', { uri });
810
+ return false;
811
+ }
812
+ }
813
+ getBySourceUri(uri) {
814
+ try {
815
+ return this.routes.find(r => r.description?.sourceTreeUri === uri ||
816
+ r.description?.videoUrl === uri);
817
+ }
818
+ catch (err) {
819
+ this.logError(err, 'getBySourceUri', { uri });
820
+ }
821
+ }
794
822
  addRoute(route, source = 'system') {
795
823
  this.routes.push(route);
796
824
  if (route.description?.isDeleted) {