incyclist-services 1.3.28 → 1.3.30

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.
@@ -4,6 +4,7 @@ export interface Card<T> {
4
4
  getData(): T;
5
5
  setData(data: T): any;
6
6
  getCardType(): any;
7
+ getTitle(): string;
7
8
  getDisplayProperties(): any;
8
9
  setInitialized(val: boolean): void;
9
10
  reset(): void;
@@ -24,7 +24,7 @@ class IncyclistService extends events_1.default {
24
24
  }
25
25
  logError(err, fn, args) {
26
26
  const logInfo = args || {};
27
- this.logger.logEvent(Object.assign(Object.assign({ message: 'Error', fn }, logInfo), { error: err.message, stack: err.stack }));
27
+ this.logEvent(Object.assign(Object.assign({ message: 'Error', fn }, logInfo), { error: err.message, stack: err.stack }));
28
28
  }
29
29
  }
30
30
  exports.IncyclistService = IncyclistService;
@@ -18,4 +18,5 @@ export default class IncyclistRoutesApi {
18
18
  getRoutePreview(routeId: string): Promise<string>;
19
19
  reload(): Promise<void>;
20
20
  protected _get(url: string, ...args: any[]): Promise<import("axios").AxiosResponse<any, any>>;
21
+ protected _reset(): void;
21
22
  }
@@ -111,5 +111,8 @@ class IncyclistRoutesApi {
111
111
  return yield api.get(baseUrl + url, ...args);
112
112
  });
113
113
  }
114
+ _reset() {
115
+ IncyclistRoutesApi._instance = undefined;
116
+ }
114
117
  }
115
118
  exports.default = IncyclistRoutesApi;
@@ -77,7 +77,7 @@ const getReferencedFileInfo = (info, referenced, scheme = 'file') => {
77
77
  return referenced.url;
78
78
  }
79
79
  if (!referenced.file) {
80
- throw new Error('referenced.file or referenced.url must be specified');
80
+ return;
81
81
  }
82
82
  let targetFileName = referenced.file;
83
83
  const regex = /([\\/])/g;
@@ -22,7 +22,7 @@ class ActiveImportCard extends base_1.BaseCard {
22
22
  const list = (0, service_1.useRouteList)().getLists().find(l => l.getId() === 'myRoutes');
23
23
  if (list) {
24
24
  list.remove(this);
25
- (0, service_1.useRouteList)().emitLists('updated');
25
+ (0, service_1.useRouteList)().emitLists('updated', true);
26
26
  }
27
27
  return observer_1.PromiseObserver.alwaysReturning(true);
28
28
  }
@@ -49,7 +49,7 @@ class ActiveImportCard extends base_1.BaseCard {
49
49
  return types_1.DEFAULT_FILTERS;
50
50
  }
51
51
  getTitle() {
52
- return types_1.DEFAULT_TITLE;
52
+ return this.error ? `${types_1.DEFAULT_TITLE}:${this.getId()}:${this.error}` : `${types_1.DEFAULT_TITLE}:${this.getId()}`;
53
53
  }
54
54
  getDisplayProperties() {
55
55
  const name = this.file.name;
@@ -28,6 +28,7 @@ export declare class FreeRideCard extends BaseCard implements Card<Route> {
28
28
  getDisplayProperties(): FreeRideDisplayProps;
29
29
  getPosition(): LatLng;
30
30
  getData(): Route;
31
+ getTitle(): string;
31
32
  setData(_data: Route): void;
32
33
  openSettings(): FreeRideSettings;
33
34
  changeSettings(props: FreeRideSettings): void;
@@ -47,6 +47,9 @@ class FreeRideCard extends base_1.BaseCard {
47
47
  getData() {
48
48
  return new route_1.Route({});
49
49
  }
50
+ getTitle() {
51
+ return 'Free Ride';
52
+ }
50
53
  setData(_data) {
51
54
  }
52
55
  openSettings() {
@@ -251,6 +251,7 @@ class RouteCard extends base_1.BaseCard {
251
251
  }
252
252
  delete() {
253
253
  try {
254
+ this.logger.logEvent({ message: 'delete card', card: this.getTitle() });
254
255
  const service = (0, service_1.getRouteList)();
255
256
  service.unselectCard(this);
256
257
  if (this.deleteObserver)
@@ -290,7 +291,8 @@ class RouteCard extends base_1.BaseCard {
290
291
  }
291
292
  this.deleteFromUIList();
292
293
  this.deleteRouteUserSettings();
293
- (0, service_1.getRouteList)().emitLists('updated');
294
+ this.logger.logEvent({ message: 'card deleted', card: this.getTitle() });
295
+ (0, service_1.getRouteList)().emitLists('updated', true);
294
296
  deleted = true;
295
297
  }
296
298
  catch (err) {
@@ -8,6 +8,7 @@ export declare class BaseCard implements Card<Route> {
8
8
  reset(): void;
9
9
  delete(): PromiseObserver<boolean>;
10
10
  getId(): string;
11
+ getTitle(): string;
11
12
  getData(): Route;
12
13
  setData(_data: Route): void;
13
14
  getCardType(): void;
@@ -19,6 +19,9 @@ class BaseCard {
19
19
  getId() {
20
20
  throw new Error("Method not implemented.");
21
21
  }
22
+ getTitle() {
23
+ throw new Error("Method not implemented.");
24
+ }
22
25
  getData() {
23
26
  throw new Error("Method not implemented.");
24
27
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
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
+ const api_1 = __importDefault(require("../../base/api"));
16
+ const consts_1 = require("../../base/api/consts");
17
+ const api_2 = require("./api");
18
+ const service_1 = require("../service");
19
+ const utils_1 = require("../../../utils");
20
+ describe('RouteApiLoader', () => {
21
+ describe('load', () => {
22
+ test('e2e test - should only be executed manually', () => __awaiter(void 0, void 0, void 0, function* () {
23
+ var _a;
24
+ const api = api_1.default.getInstance();
25
+ api['getBaseUrl'] = jest.fn().mockReturnValue(consts_1.DEFAULT_ROUTE_API);
26
+ const list = new service_1.RouteListService();
27
+ list.createPreview = jest.fn().mockResolvedValue(true);
28
+ const loader = new api_2.RoutesApiLoader();
29
+ const routes = [];
30
+ const run = () => new Promise(done => {
31
+ let added = 0;
32
+ let updated = 0;
33
+ const observer = loader.load();
34
+ observer.on('route.added', route => {
35
+ routes.push(route);
36
+ added++;
37
+ });
38
+ observer.on('route.updated', route => {
39
+ updated++;
40
+ });
41
+ observer.on('done', () => __awaiter(void 0, void 0, void 0, function* () {
42
+ console.log('added:', added);
43
+ console.log('updated:', updated);
44
+ yield (0, utils_1.waitNextTick)();
45
+ done({ added, updated });
46
+ }));
47
+ });
48
+ const run1 = yield run();
49
+ console.log(run1);
50
+ expect(routes.length).toBe(14);
51
+ expect(run1.updated).toBe(6);
52
+ const existing = Array.from(routes);
53
+ api['getRouteDescriptionFromDB'] = jest.fn((id) => existing.find(r => r.description.id === id));
54
+ const run2 = yield run();
55
+ expect(routes.length).toBe(14);
56
+ expect(run2.updated).toBe(0);
57
+ expect(run2.added).toBe(0);
58
+ const updateTarget = existing.find(r => { var _a; return (_a = r.description.version) !== null && _a !== void 0 ? _a : 0 > 1; });
59
+ if (updateTarget) {
60
+ const prevVersion = (_a = updateTarget.description.version) !== null && _a !== void 0 ? _a : 1;
61
+ updateTarget.description.version = prevVersion - 1;
62
+ const run3 = yield run();
63
+ expect(routes.length).toBe(14);
64
+ expect(run3.updated).toBe(1);
65
+ expect(run3.added).toBe(0);
66
+ }
67
+ }), 20000);
68
+ });
69
+ });
@@ -76,6 +76,7 @@ class RoutesApiLoader extends types_1.Loader {
76
76
  yield this.loadDetails(loadDetailRequired);
77
77
  this.loadObserver.emit('done');
78
78
  yield (0, utils_1.waitNextTick)();
79
+ this.loadObserver.stop();
79
80
  delete this.loadObserver;
80
81
  });
81
82
  }
@@ -39,6 +39,5 @@ export declare class RoutesDbLoader extends DBLoader<RouteInfoDBEntry> {
39
39
  protected getRoutesRepo(): JsonRepository;
40
40
  protected getDetailsRepo(route: Route): JsonRepository;
41
41
  protected writeDetails(route: Route): Promise<void>;
42
- protected deleteRoute(id: string): Promise<PromiseObserver<void>>;
43
- protected _deleteRoute(id: any): Promise<void>;
42
+ protected deleteRouteDetails(route: Route): Promise<void>;
44
43
  }
@@ -46,6 +46,9 @@ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, p
46
46
  if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
47
47
  return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
48
48
  };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
49
52
  Object.defineProperty(exports, "__esModule", { value: true });
50
53
  exports.RoutesDbLoader = void 0;
51
54
  const api_1 = require("../../../api");
@@ -55,6 +58,7 @@ const LegacyDB_1 = require("./LegacyDB");
55
58
  const DBLoader_1 = require("./DBLoader");
56
59
  const utils_1 = require("../../../utils");
57
60
  const route_1 = require("../../base/utils/route");
61
+ const clone_1 = __importDefault(require("../../../utils/clone"));
58
62
  let RoutesDbLoader = (() => {
59
63
  let _classDecorators = [types_1.Singleton];
60
64
  let _classDescriptor;
@@ -74,15 +78,20 @@ let RoutesDbLoader = (() => {
74
78
  return JSON.stringify(json);
75
79
  }
76
80
  catch (_a) { } };
77
- let prev;
81
+ const updatedDescr = this.buildRouteDBInfo(route.description);
78
82
  const idx = this.routeDescriptions.findIndex(d => d.id === route.description.id);
79
- if (idx === -1)
80
- this.routeDescriptions.push(route.description);
83
+ let changed = false;
84
+ if (idx === -1) {
85
+ this.routeDescriptions.push((0, clone_1.default)(updatedDescr));
86
+ changed = true;
87
+ }
81
88
  else {
82
- prev = stringify(this.buildRouteDBInfo(this.routeDescriptions[idx]));
83
- this.routeDescriptions[idx] = route.description;
89
+ const prev = stringify(this.routeDescriptions[idx]);
90
+ const updated = stringify(updatedDescr);
91
+ changed = (prev !== updated);
92
+ if (changed)
93
+ this.routeDescriptions[idx] = (0, clone_1.default)(updatedDescr);
84
94
  }
85
- const changed = !prev || stringify(this.buildRouteDBInfo(this.routeDescriptions[idx])) !== prev;
86
95
  if (changed) {
87
96
  this.isDirty = true;
88
97
  this.write();
@@ -103,8 +112,8 @@ let RoutesDbLoader = (() => {
103
112
  throw new Error('route not found');
104
113
  this.routeDescriptions.splice(idx, 1);
105
114
  this.isDirty = true;
106
- this.write();
107
- yield this.deleteRoute(id);
115
+ yield this.writeRepo();
116
+ yield this.deleteRouteDetails(route);
108
117
  });
109
118
  }
110
119
  getDescription(id) {
@@ -127,6 +136,7 @@ let RoutesDbLoader = (() => {
127
136
  const save = () => __awaiter(this, void 0, void 0, function* () {
128
137
  try {
129
138
  yield this.routesRepo.write('db', this.routeDescriptions.map(this.buildRouteDBInfo.bind(this)));
139
+ this.isDirty = false;
130
140
  }
131
141
  catch (err) {
132
142
  this.logger.logEvent({ message: 'could not safe repo', error: err.message });
@@ -302,29 +312,29 @@ let RoutesDbLoader = (() => {
302
312
  process.nextTick(() => { delete this.routesSaveObserver[id]; });
303
313
  });
304
314
  }
305
- deleteRoute(id) {
315
+ deleteRouteDetails(route) {
306
316
  return __awaiter(this, void 0, void 0, function* () {
307
- if (!this.routesRepo)
317
+ const repo = this.getDetailsRepo(route);
318
+ if (!repo)
308
319
  return;
320
+ const id = route.description.id;
309
321
  if (this.routesSaveObserver[id])
310
- return this.routesSaveObserver[id];
311
- this.routesSaveObserver[id] = new observer_1.PromiseObserver(this._deleteRoute(id));
322
+ yield this.routesSaveObserver[id].wait();
323
+ const _deleteRoute = (id) => __awaiter(this, void 0, void 0, function* () {
324
+ yield (0, utils_1.waitNextTick)();
325
+ try {
326
+ yield repo.delete(id);
327
+ (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
328
+ }
329
+ catch (err) {
330
+ (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
331
+ throw err;
332
+ }
333
+ });
334
+ this.routesSaveObserver[id] = new observer_1.PromiseObserver(_deleteRoute(id));
312
335
  yield this.routesSaveObserver[id].start();
313
336
  });
314
337
  }
315
- _deleteRoute(id) {
316
- return __awaiter(this, void 0, void 0, function* () {
317
- yield (0, utils_1.waitNextTick)();
318
- try {
319
- yield this.routesRepo.delete(id);
320
- (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
321
- }
322
- catch (err) {
323
- (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
324
- throw err;
325
- }
326
- });
327
- }
328
338
  };
329
339
  __setFunctionName(_classThis, "RoutesDbLoader");
330
340
  (() => {
@@ -0,0 +1,44 @@
1
+ import { JsonRepository } from "../../../api";
2
+ import { Observer, PromiseObserver } from "../../../base/types/observer";
3
+ import { Route } from "../../base/model/route";
4
+ import { RouteInfo } from "../../base/types";
5
+ import { RoutesLegacyDbLoader } from "./LegacyDB";
6
+ import { RouteInfoDBEntry } from "./types";
7
+ import { DBLoader } from "./DBLoader";
8
+ import { RouteApiDetail } from "../../base/api/types";
9
+ export declare class RoutesDbLoader extends DBLoader<RouteInfoDBEntry> {
10
+ protected loadObserver: Observer;
11
+ protected saveObserver: PromiseObserver<void>;
12
+ protected videosRepo: JsonRepository;
13
+ protected routesRepo: JsonRepository;
14
+ protected routeDescriptions: Array<RouteInfoDBEntry>;
15
+ protected tsLastWrite: number;
16
+ protected isDirty: boolean;
17
+ protected routesSaveObserver: {
18
+ [index: string]: PromiseObserver<void>;
19
+ };
20
+ constructor();
21
+ save(route: Route, enforcedWriteDetails?: boolean): Promise<void>;
22
+ delete(route: Route): Promise<void>;
23
+ getDescription(id: string): RouteInfo;
24
+ getDetails(id: string): Promise<RouteApiDetail>;
25
+ protected writeRepo(): Promise<void>;
26
+ protected write(): void;
27
+ protected getLegacyLoader(): RoutesLegacyDbLoader;
28
+ protected buildRouteInfo(descr: RouteInfoDBEntry): RouteInfo;
29
+ protected buildRouteDBInfo(descr: RouteInfo): RouteInfoDBEntry;
30
+ protected loadFromLegacy(): Promise<Array<RouteInfoDBEntry>>;
31
+ protected loadDescriptions(): Promise<Array<RouteInfoDBEntry>>;
32
+ protected removeDuplicates(routes: Array<RouteInfoDBEntry>): any[];
33
+ protected verifyImportDate(routes: Array<RouteInfoDBEntry>): void;
34
+ protected loadDetailRecord(target: Route | RouteInfo): Promise<RouteApiDetail>;
35
+ private validateDetails;
36
+ private validateDescription;
37
+ protected loadDetails(route: Route, alreadyAdded?: boolean): Promise<void>;
38
+ protected getVideoRepo(): JsonRepository;
39
+ protected getRoutesRepo(): JsonRepository;
40
+ protected getDetailsRepo(route: Route): JsonRepository;
41
+ protected writeDetails(route: Route): Promise<void>;
42
+ protected deleteRoute(id: string): Promise<PromiseObserver<void>>;
43
+ protected _deleteRoute(id: any): Promise<void>;
44
+ }
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
3
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
4
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
5
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
7
+ var _, done = false;
8
+ for (var i = decorators.length - 1; i >= 0; i--) {
9
+ var context = {};
10
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
11
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
12
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
13
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
14
+ if (kind === "accessor") {
15
+ if (result === void 0) continue;
16
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
17
+ if (_ = accept(result.get)) descriptor.get = _;
18
+ if (_ = accept(result.set)) descriptor.set = _;
19
+ if (_ = accept(result.init)) initializers.unshift(_);
20
+ }
21
+ else if (_ = accept(result)) {
22
+ if (kind === "field") initializers.unshift(_);
23
+ else descriptor[key] = _;
24
+ }
25
+ }
26
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
27
+ done = true;
28
+ };
29
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
30
+ var useValue = arguments.length > 2;
31
+ for (var i = 0; i < initializers.length; i++) {
32
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
33
+ }
34
+ return useValue ? value : void 0;
35
+ };
36
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
37
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
38
+ return new (P || (P = Promise))(function (resolve, reject) {
39
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
40
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
41
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
42
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
43
+ });
44
+ };
45
+ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
46
+ if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
47
+ return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.RoutesDbLoader = void 0;
54
+ const api_1 = require("../../../api");
55
+ const types_1 = require("../../../base/types");
56
+ const observer_1 = require("../../../base/types/observer");
57
+ const LegacyDB_1 = require("./LegacyDB");
58
+ const DBLoader_1 = require("./DBLoader");
59
+ const utils_1 = require("../../../utils");
60
+ const route_1 = require("../../base/utils/route");
61
+ const clone_1 = __importDefault(require("../../../utils/clone"));
62
+ let RoutesDbLoader = (() => {
63
+ let _classDecorators = [types_1.Singleton];
64
+ let _classDescriptor;
65
+ let _classExtraInitializers = [];
66
+ let _classThis;
67
+ let _classSuper = DBLoader_1.DBLoader;
68
+ var RoutesDbLoader = _classThis = class extends _classSuper {
69
+ constructor() {
70
+ super();
71
+ this.routesSaveObserver = {};
72
+ this.routeDescriptions = [];
73
+ this.isDirty = true;
74
+ }
75
+ save(route_2) {
76
+ return __awaiter(this, arguments, void 0, function* (route, enforcedWriteDetails = false) {
77
+ const stringify = (json) => { try {
78
+ return JSON.stringify(json);
79
+ }
80
+ catch (_a) { } };
81
+ const updatedDescr = this.buildRouteDBInfo(route.description);
82
+ const idx = this.routeDescriptions.findIndex(d => d.id === route.description.id);
83
+ let changed = false;
84
+ if (idx === -1) {
85
+ this.routeDescriptions.push((0, clone_1.default)(updatedDescr));
86
+ changed = true;
87
+ }
88
+ else {
89
+ const prev = stringify(this.routeDescriptions[idx]);
90
+ const updated = stringify(updatedDescr);
91
+ changed = (prev !== updated);
92
+ if (changed)
93
+ this.routeDescriptions[idx] = (0, clone_1.default)(updatedDescr);
94
+ }
95
+ if (changed) {
96
+ this.isDirty = true;
97
+ this.write();
98
+ this.writeDetails(route);
99
+ }
100
+ else {
101
+ const details = yield this.loadDetailRecord(route);
102
+ if (!details || enforcedWriteDetails)
103
+ this.writeDetails(route);
104
+ }
105
+ });
106
+ }
107
+ delete(route) {
108
+ return __awaiter(this, void 0, void 0, function* () {
109
+ const id = route.description.id;
110
+ const idx = this.routeDescriptions.findIndex(d => d.id === id);
111
+ if (idx == -1)
112
+ throw new Error('route not found');
113
+ this.routeDescriptions.splice(idx, 1);
114
+ this.isDirty = true;
115
+ this.write();
116
+ yield this.deleteRoute(id);
117
+ });
118
+ }
119
+ getDescription(id) {
120
+ const descr = this.routeDescriptions.find(d => d.id === id);
121
+ return descr;
122
+ }
123
+ getDetails(id) {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ const descr = this.routeDescriptions.find(d => d.id === id);
126
+ if (!descr)
127
+ return;
128
+ const details = yield this.loadDetailRecord(descr);
129
+ return details;
130
+ });
131
+ }
132
+ writeRepo() {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ if (this.saveObserver)
135
+ yield this.saveObserver.wait();
136
+ const save = () => __awaiter(this, void 0, void 0, function* () {
137
+ try {
138
+ yield this.routesRepo.write('db', this.routeDescriptions.map(this.buildRouteDBInfo.bind(this)));
139
+ }
140
+ catch (err) {
141
+ this.logger.logEvent({ message: 'could not safe repo', error: err.message });
142
+ }
143
+ });
144
+ this.saveObserver = new observer_1.PromiseObserver(save());
145
+ yield this.saveObserver.start();
146
+ process.nextTick(() => { delete this.saveObserver; });
147
+ });
148
+ }
149
+ write() {
150
+ if (this.isDirty && (this.tsLastWrite === undefined || Date.now() - this.tsLastWrite >= 1000)) {
151
+ this.isDirty = false;
152
+ this.tsLastWrite = Date.now();
153
+ this.writeRepo();
154
+ }
155
+ if (this.isDirty && Date.now() - this.tsLastWrite < 1000) {
156
+ setTimeout(() => { this.write(); }, this.tsLastWrite + 1000 - Date.now());
157
+ }
158
+ }
159
+ getLegacyLoader() {
160
+ return new LegacyDB_1.RoutesLegacyDbLoader();
161
+ }
162
+ buildRouteInfo(descr) {
163
+ return descr;
164
+ }
165
+ buildRouteDBInfo(descr) {
166
+ const data = Object.assign({}, descr);
167
+ delete data.points;
168
+ return data;
169
+ }
170
+ loadFromLegacy() {
171
+ return __awaiter(this, void 0, void 0, function* () {
172
+ return new Promise(done => {
173
+ const observer = this.getLegacyLoader().load();
174
+ const descriptions = [];
175
+ observer.on('route.added', (r) => { descriptions.push(r.description); });
176
+ observer.on('done', () => done(descriptions));
177
+ });
178
+ });
179
+ }
180
+ loadDescriptions() {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ const descriptions = yield this.getRoutesRepo().read('db');
183
+ if (descriptions) {
184
+ const routes = descriptions;
185
+ const cleaned = this.removeDuplicates(routes);
186
+ return cleaned;
187
+ }
188
+ const legacy = yield this.loadFromLegacy();
189
+ return legacy;
190
+ });
191
+ }
192
+ removeDuplicates(routes) {
193
+ const ids = routes.map(r => r.legacyId || r.id);
194
+ const uniqueIds = ids.filter((d, pos) => ids.indexOf(d) === pos);
195
+ const cleaned = [];
196
+ uniqueIds.forEach(id => {
197
+ const routeWithLegacy = routes.find(r => r.legacyId === id);
198
+ const route = routeWithLegacy || routes.find(r => r.id === id);
199
+ if (route)
200
+ cleaned.push(route);
201
+ });
202
+ return cleaned;
203
+ }
204
+ verifyImportDate(routes) {
205
+ let changed = false;
206
+ routes.forEach(r => {
207
+ if (!r.tsImported) {
208
+ r.tsImported = Date.now();
209
+ changed = true;
210
+ }
211
+ });
212
+ if (changed) {
213
+ this.isDirty = true;
214
+ this.write();
215
+ }
216
+ }
217
+ loadDetailRecord(target) {
218
+ return __awaiter(this, void 0, void 0, function* () {
219
+ var _a;
220
+ const description = (target.description || target);
221
+ const repo = (description === null || description === void 0 ? void 0 : description.hasVideo) ? this.getVideosRepo() : this.getRoutesRepo();
222
+ let details;
223
+ try {
224
+ const id = description.legacyId || description.id;
225
+ details = (yield repo.read(id));
226
+ description.originalName = details.title;
227
+ if (description.hasVideo) {
228
+ description.segments = ((_a = details.video) === null || _a === void 0 ? void 0 : _a.selectableSegments) || (details === null || details === void 0 ? void 0 : details.selectableSegments);
229
+ }
230
+ }
231
+ catch (err) {
232
+ this.logger.logEvent({ message: 'could not load route details', id: (description === null || description === void 0 ? void 0 : description.legacyId) || (description === null || description === void 0 ? void 0 : description.id), title: description === null || description === void 0 ? void 0 : description.title, reason: err.message, stack: err.stack });
233
+ if (description.id && description.legacyId !== description.id) {
234
+ try {
235
+ details = (yield repo.read(description.id));
236
+ description.originalName = details.title;
237
+ }
238
+ catch (err) {
239
+ this.logger.logEvent({ message: 'could not load route details', id: (description === null || description === void 0 ? void 0 : description.legacyId) || (description === null || description === void 0 ? void 0 : description.id), title: description === null || description === void 0 ? void 0 : description.title, reason: err.message, stack: err.stack });
240
+ }
241
+ }
242
+ }
243
+ if (!details) {
244
+ return;
245
+ }
246
+ this.validateDetails(details);
247
+ this.validateDescription(description, details);
248
+ return details;
249
+ });
250
+ }
251
+ validateDetails(details) {
252
+ if (!details.points || !Array.isArray(details.points)) {
253
+ details.points = details.decoded;
254
+ delete details.decoded;
255
+ }
256
+ if (details.localizedTitle && typeof (details.localizedTitle) === 'string') {
257
+ details.localizedTitle = { en: details.localizedTitle };
258
+ }
259
+ (0, route_1.validateDistance)(details.points);
260
+ (0, route_1.updateSlopes)(details.points);
261
+ }
262
+ validateDescription(description, details) {
263
+ if (!description.elevation) {
264
+ description.elevation = (0, route_1.getTotalElevation)(details);
265
+ }
266
+ }
267
+ loadDetails(route, alreadyAdded) {
268
+ return __awaiter(this, void 0, void 0, function* () {
269
+ const details = yield this.loadDetailRecord(route);
270
+ (0, route_1.addDetails)(route, details);
271
+ this.verifyRouteHash(route);
272
+ if (alreadyAdded)
273
+ this.emitRouteUpdate(route);
274
+ this.emitRouteAdded(route);
275
+ this.verifyCountry(route);
276
+ });
277
+ }
278
+ getVideoRepo() {
279
+ if (!this.videosRepo)
280
+ this.videosRepo = api_1.JsonRepository.create('videos');
281
+ return this.videosRepo;
282
+ }
283
+ getRoutesRepo() {
284
+ if (!this.routesRepo)
285
+ this.routesRepo = api_1.JsonRepository.create('routes');
286
+ return this.routesRepo;
287
+ }
288
+ getDetailsRepo(route) {
289
+ var _a;
290
+ return ((_a = route === null || route === void 0 ? void 0 : route.description) === null || _a === void 0 ? void 0 : _a.hasVideo) ? this.getVideoRepo() : this.getRoutesRepo();
291
+ }
292
+ writeDetails(route) {
293
+ return __awaiter(this, void 0, void 0, function* () {
294
+ const repo = this.getDetailsRepo(route);
295
+ if (!repo) {
296
+ return;
297
+ }
298
+ const id = route.description.legacyId || route.description.id;
299
+ if (this.routesSaveObserver[id]) {
300
+ yield this.routesSaveObserver[id].wait();
301
+ }
302
+ const save = () => __awaiter(this, void 0, void 0, function* () {
303
+ try {
304
+ yield repo.write(id, route.details);
305
+ }
306
+ catch (err) {
307
+ }
308
+ });
309
+ this.routesSaveObserver[id] = new observer_1.PromiseObserver(save());
310
+ yield this.routesSaveObserver[id].start();
311
+ process.nextTick(() => { delete this.routesSaveObserver[id]; });
312
+ });
313
+ }
314
+ deleteRoute(id) {
315
+ return __awaiter(this, void 0, void 0, function* () {
316
+ if (!this.routesRepo)
317
+ return;
318
+ if (this.routesSaveObserver[id])
319
+ return this.routesSaveObserver[id];
320
+ this.routesSaveObserver[id] = new observer_1.PromiseObserver(this._deleteRoute(id));
321
+ yield this.routesSaveObserver[id].start();
322
+ });
323
+ }
324
+ _deleteRoute(id) {
325
+ return __awaiter(this, void 0, void 0, function* () {
326
+ yield (0, utils_1.waitNextTick)();
327
+ try {
328
+ yield this.routesRepo.delete(id);
329
+ (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
330
+ }
331
+ catch (err) {
332
+ (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
333
+ throw err;
334
+ }
335
+ });
336
+ }
337
+ };
338
+ __setFunctionName(_classThis, "RoutesDbLoader");
339
+ (() => {
340
+ var _a;
341
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create((_a = _classSuper[Symbol.metadata]) !== null && _a !== void 0 ? _a : null) : void 0;
342
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
343
+ RoutesDbLoader = _classThis = _classDescriptor.value;
344
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
345
+ __runInitializers(_classThis, _classExtraInitializers);
346
+ })();
347
+ return RoutesDbLoader = _classThis;
348
+ })();
349
+ exports.RoutesDbLoader = RoutesDbLoader;
@@ -0,0 +1,44 @@
1
+ import { JsonRepository } from "../../../api";
2
+ import { Observer, PromiseObserver } from "../../../base/types/observer";
3
+ import { Route } from "../../base/model/route";
4
+ import { RouteInfo } from "../../base/types";
5
+ import { RoutesLegacyDbLoader } from "./LegacyDB";
6
+ import { RouteInfoDBEntry } from "./types";
7
+ import { DBLoader } from "./DBLoader";
8
+ import { RouteApiDetail } from "../../base/api/types";
9
+ export declare class RoutesDbLoader extends DBLoader<RouteInfoDBEntry> {
10
+ protected loadObserver: Observer;
11
+ protected saveObserver: PromiseObserver<void>;
12
+ protected videosRepo: JsonRepository;
13
+ protected routesRepo: JsonRepository;
14
+ protected routeDescriptions: Array<RouteInfoDBEntry>;
15
+ protected tsLastWrite: number;
16
+ protected isDirty: boolean;
17
+ protected routesSaveObserver: {
18
+ [index: string]: PromiseObserver<void>;
19
+ };
20
+ constructor();
21
+ save(route: Route, enforcedWriteDetails?: boolean): Promise<void>;
22
+ delete(route: Route): Promise<void>;
23
+ getDescription(id: string): RouteInfo;
24
+ getDetails(id: string): Promise<RouteApiDetail>;
25
+ protected writeRepo(): Promise<void>;
26
+ protected write(): void;
27
+ protected getLegacyLoader(): RoutesLegacyDbLoader;
28
+ protected buildRouteInfo(descr: RouteInfoDBEntry): RouteInfo;
29
+ protected buildRouteDBInfo(descr: RouteInfo): RouteInfoDBEntry;
30
+ protected loadFromLegacy(): Promise<Array<RouteInfoDBEntry>>;
31
+ protected loadDescriptions(): Promise<Array<RouteInfoDBEntry>>;
32
+ protected removeDuplicates(routes: Array<RouteInfoDBEntry>): any[];
33
+ protected verifyImportDate(routes: Array<RouteInfoDBEntry>): void;
34
+ protected loadDetailRecord(target: Route | RouteInfo): Promise<RouteApiDetail>;
35
+ private validateDetails;
36
+ private validateDescription;
37
+ protected loadDetails(route: Route, alreadyAdded?: boolean): Promise<void>;
38
+ protected getVideoRepo(): JsonRepository;
39
+ protected getRoutesRepo(): JsonRepository;
40
+ protected getDetailsRepo(route: Route): JsonRepository;
41
+ protected writeDetails(route: Route): Promise<void>;
42
+ protected deleteRoute(id: string): Promise<PromiseObserver<void>>;
43
+ protected _deleteRoute(id: any): Promise<void>;
44
+ }
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
3
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
4
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
5
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
7
+ var _, done = false;
8
+ for (var i = decorators.length - 1; i >= 0; i--) {
9
+ var context = {};
10
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
11
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
12
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
13
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
14
+ if (kind === "accessor") {
15
+ if (result === void 0) continue;
16
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
17
+ if (_ = accept(result.get)) descriptor.get = _;
18
+ if (_ = accept(result.set)) descriptor.set = _;
19
+ if (_ = accept(result.init)) initializers.unshift(_);
20
+ }
21
+ else if (_ = accept(result)) {
22
+ if (kind === "field") initializers.unshift(_);
23
+ else descriptor[key] = _;
24
+ }
25
+ }
26
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
27
+ done = true;
28
+ };
29
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
30
+ var useValue = arguments.length > 2;
31
+ for (var i = 0; i < initializers.length; i++) {
32
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
33
+ }
34
+ return useValue ? value : void 0;
35
+ };
36
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
37
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
38
+ return new (P || (P = Promise))(function (resolve, reject) {
39
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
40
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
41
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
42
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
43
+ });
44
+ };
45
+ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
46
+ if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
47
+ return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.RoutesDbLoader = void 0;
51
+ const api_1 = require("../../../api");
52
+ const types_1 = require("../../../base/types");
53
+ const observer_1 = require("../../../base/types/observer");
54
+ const LegacyDB_1 = require("./LegacyDB");
55
+ const DBLoader_1 = require("./DBLoader");
56
+ const utils_1 = require("../../../utils");
57
+ const route_1 = require("../../base/utils/route");
58
+ let RoutesDbLoader = (() => {
59
+ let _classDecorators = [types_1.Singleton];
60
+ let _classDescriptor;
61
+ let _classExtraInitializers = [];
62
+ let _classThis;
63
+ let _classSuper = DBLoader_1.DBLoader;
64
+ var RoutesDbLoader = _classThis = class extends _classSuper {
65
+ constructor() {
66
+ super();
67
+ this.routesSaveObserver = {};
68
+ this.routeDescriptions = [];
69
+ this.isDirty = true;
70
+ }
71
+ save(route_2) {
72
+ return __awaiter(this, arguments, void 0, function* (route, enforcedWriteDetails = false) {
73
+ const stringify = (json) => { try {
74
+ return JSON.stringify(json);
75
+ }
76
+ catch (_a) { } };
77
+ let prev;
78
+ const idx = this.routeDescriptions.findIndex(d => d.id === route.description.id);
79
+ if (idx === -1)
80
+ this.routeDescriptions.push(route.description);
81
+ else {
82
+ prev = stringify(this.buildRouteDBInfo(this.routeDescriptions[idx]));
83
+ this.routeDescriptions[idx] = route.description;
84
+ }
85
+ const changed = !prev || stringify(this.buildRouteDBInfo(this.routeDescriptions[idx])) !== prev;
86
+ if (changed) {
87
+ this.isDirty = true;
88
+ this.write();
89
+ this.writeDetails(route);
90
+ }
91
+ else {
92
+ const details = yield this.loadDetailRecord(route);
93
+ if (!details || enforcedWriteDetails)
94
+ this.writeDetails(route);
95
+ }
96
+ });
97
+ }
98
+ delete(route) {
99
+ return __awaiter(this, void 0, void 0, function* () {
100
+ const id = route.description.id;
101
+ const idx = this.routeDescriptions.findIndex(d => d.id === id);
102
+ if (idx == -1)
103
+ throw new Error('route not found');
104
+ this.routeDescriptions.splice(idx, 1);
105
+ this.isDirty = true;
106
+ this.write();
107
+ yield this.deleteRoute(id);
108
+ });
109
+ }
110
+ getDescription(id) {
111
+ const descr = this.routeDescriptions.find(d => d.id === id);
112
+ return descr;
113
+ }
114
+ getDetails(id) {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ const descr = this.routeDescriptions.find(d => d.id === id);
117
+ if (!descr)
118
+ return;
119
+ const details = yield this.loadDetailRecord(descr);
120
+ return details;
121
+ });
122
+ }
123
+ writeRepo() {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ if (this.saveObserver)
126
+ yield this.saveObserver.wait();
127
+ const save = () => __awaiter(this, void 0, void 0, function* () {
128
+ try {
129
+ yield this.routesRepo.write('db', this.routeDescriptions.map(this.buildRouteDBInfo.bind(this)));
130
+ }
131
+ catch (err) {
132
+ this.logger.logEvent({ message: 'could not safe repo', error: err.message });
133
+ }
134
+ });
135
+ this.saveObserver = new observer_1.PromiseObserver(save());
136
+ yield this.saveObserver.start();
137
+ process.nextTick(() => { delete this.saveObserver; });
138
+ });
139
+ }
140
+ write() {
141
+ if (this.isDirty && (this.tsLastWrite === undefined || Date.now() - this.tsLastWrite >= 1000)) {
142
+ this.isDirty = false;
143
+ this.tsLastWrite = Date.now();
144
+ this.writeRepo();
145
+ }
146
+ if (this.isDirty && Date.now() - this.tsLastWrite < 1000) {
147
+ setTimeout(() => { this.write(); }, this.tsLastWrite + 1000 - Date.now());
148
+ }
149
+ }
150
+ getLegacyLoader() {
151
+ return new LegacyDB_1.RoutesLegacyDbLoader();
152
+ }
153
+ buildRouteInfo(descr) {
154
+ return descr;
155
+ }
156
+ buildRouteDBInfo(descr) {
157
+ const data = Object.assign({}, descr);
158
+ delete data.points;
159
+ return data;
160
+ }
161
+ loadFromLegacy() {
162
+ return __awaiter(this, void 0, void 0, function* () {
163
+ return new Promise(done => {
164
+ const observer = this.getLegacyLoader().load();
165
+ const descriptions = [];
166
+ observer.on('route.added', (r) => { descriptions.push(r.description); });
167
+ observer.on('done', () => done(descriptions));
168
+ });
169
+ });
170
+ }
171
+ loadDescriptions() {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ const descriptions = yield this.getRoutesRepo().read('db');
174
+ if (descriptions) {
175
+ const routes = descriptions;
176
+ const cleaned = this.removeDuplicates(routes);
177
+ return cleaned;
178
+ }
179
+ const legacy = yield this.loadFromLegacy();
180
+ return legacy;
181
+ });
182
+ }
183
+ removeDuplicates(routes) {
184
+ const ids = routes.map(r => r.legacyId || r.id);
185
+ const uniqueIds = ids.filter((d, pos) => ids.indexOf(d) === pos);
186
+ const cleaned = [];
187
+ uniqueIds.forEach(id => {
188
+ const routeWithLegacy = routes.find(r => r.legacyId === id);
189
+ const route = routeWithLegacy || routes.find(r => r.id === id);
190
+ if (route)
191
+ cleaned.push(route);
192
+ });
193
+ return cleaned;
194
+ }
195
+ verifyImportDate(routes) {
196
+ let changed = false;
197
+ routes.forEach(r => {
198
+ if (!r.tsImported) {
199
+ r.tsImported = Date.now();
200
+ changed = true;
201
+ }
202
+ });
203
+ if (changed) {
204
+ this.isDirty = true;
205
+ this.write();
206
+ }
207
+ }
208
+ loadDetailRecord(target) {
209
+ return __awaiter(this, void 0, void 0, function* () {
210
+ var _a;
211
+ const description = (target.description || target);
212
+ const repo = (description === null || description === void 0 ? void 0 : description.hasVideo) ? this.getVideosRepo() : this.getRoutesRepo();
213
+ let details;
214
+ try {
215
+ const id = description.legacyId || description.id;
216
+ details = (yield repo.read(id));
217
+ description.originalName = details.title;
218
+ if (description.hasVideo) {
219
+ description.segments = ((_a = details.video) === null || _a === void 0 ? void 0 : _a.selectableSegments) || (details === null || details === void 0 ? void 0 : details.selectableSegments);
220
+ }
221
+ }
222
+ catch (err) {
223
+ this.logger.logEvent({ message: 'could not load route details', id: (description === null || description === void 0 ? void 0 : description.legacyId) || (description === null || description === void 0 ? void 0 : description.id), title: description === null || description === void 0 ? void 0 : description.title, reason: err.message, stack: err.stack });
224
+ if (description.id && description.legacyId !== description.id) {
225
+ try {
226
+ details = (yield repo.read(description.id));
227
+ description.originalName = details.title;
228
+ }
229
+ catch (err) {
230
+ this.logger.logEvent({ message: 'could not load route details', id: (description === null || description === void 0 ? void 0 : description.legacyId) || (description === null || description === void 0 ? void 0 : description.id), title: description === null || description === void 0 ? void 0 : description.title, reason: err.message, stack: err.stack });
231
+ }
232
+ }
233
+ }
234
+ if (!details) {
235
+ return;
236
+ }
237
+ this.validateDetails(details);
238
+ this.validateDescription(description, details);
239
+ return details;
240
+ });
241
+ }
242
+ validateDetails(details) {
243
+ if (!details.points || !Array.isArray(details.points)) {
244
+ details.points = details.decoded;
245
+ delete details.decoded;
246
+ }
247
+ if (details.localizedTitle && typeof (details.localizedTitle) === 'string') {
248
+ details.localizedTitle = { en: details.localizedTitle };
249
+ }
250
+ (0, route_1.validateDistance)(details.points);
251
+ (0, route_1.updateSlopes)(details.points);
252
+ }
253
+ validateDescription(description, details) {
254
+ if (!description.elevation) {
255
+ description.elevation = (0, route_1.getTotalElevation)(details);
256
+ }
257
+ }
258
+ loadDetails(route, alreadyAdded) {
259
+ return __awaiter(this, void 0, void 0, function* () {
260
+ const details = yield this.loadDetailRecord(route);
261
+ (0, route_1.addDetails)(route, details);
262
+ this.verifyRouteHash(route);
263
+ if (alreadyAdded)
264
+ this.emitRouteUpdate(route);
265
+ this.emitRouteAdded(route);
266
+ this.verifyCountry(route);
267
+ });
268
+ }
269
+ getVideoRepo() {
270
+ if (!this.videosRepo)
271
+ this.videosRepo = api_1.JsonRepository.create('videos');
272
+ return this.videosRepo;
273
+ }
274
+ getRoutesRepo() {
275
+ if (!this.routesRepo)
276
+ this.routesRepo = api_1.JsonRepository.create('routes');
277
+ return this.routesRepo;
278
+ }
279
+ getDetailsRepo(route) {
280
+ var _a;
281
+ return ((_a = route === null || route === void 0 ? void 0 : route.description) === null || _a === void 0 ? void 0 : _a.hasVideo) ? this.getVideoRepo() : this.getRoutesRepo();
282
+ }
283
+ writeDetails(route) {
284
+ return __awaiter(this, void 0, void 0, function* () {
285
+ const repo = this.getDetailsRepo(route);
286
+ if (!repo) {
287
+ return;
288
+ }
289
+ const id = route.description.legacyId || route.description.id;
290
+ if (this.routesSaveObserver[id]) {
291
+ yield this.routesSaveObserver[id].wait();
292
+ }
293
+ const save = () => __awaiter(this, void 0, void 0, function* () {
294
+ try {
295
+ yield repo.write(id, route.details);
296
+ }
297
+ catch (err) {
298
+ }
299
+ });
300
+ this.routesSaveObserver[id] = new observer_1.PromiseObserver(save());
301
+ yield this.routesSaveObserver[id].start();
302
+ process.nextTick(() => { delete this.routesSaveObserver[id]; });
303
+ });
304
+ }
305
+ deleteRoute(id) {
306
+ return __awaiter(this, void 0, void 0, function* () {
307
+ if (!this.routesRepo)
308
+ return;
309
+ if (this.routesSaveObserver[id])
310
+ return this.routesSaveObserver[id];
311
+ this.routesSaveObserver[id] = new observer_1.PromiseObserver(this._deleteRoute(id));
312
+ yield this.routesSaveObserver[id].start();
313
+ });
314
+ }
315
+ _deleteRoute(id) {
316
+ return __awaiter(this, void 0, void 0, function* () {
317
+ yield (0, utils_1.waitNextTick)();
318
+ try {
319
+ yield this.routesRepo.delete(id);
320
+ (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
321
+ }
322
+ catch (err) {
323
+ (0, utils_1.waitNextTick)().then(() => { delete this.routesSaveObserver[id]; });
324
+ throw err;
325
+ }
326
+ });
327
+ }
328
+ };
329
+ __setFunctionName(_classThis, "RoutesDbLoader");
330
+ (() => {
331
+ var _a;
332
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create((_a = _classSuper[Symbol.metadata]) !== null && _a !== void 0 ? _a : null) : void 0;
333
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
334
+ RoutesDbLoader = _classThis = _classDescriptor.value;
335
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
336
+ __runInitializers(_classThis, _classExtraInitializers);
337
+ })();
338
+ return RoutesDbLoader = _classThis;
339
+ })();
340
+ exports.RoutesDbLoader = RoutesDbLoader;
@@ -66,6 +66,7 @@ export declare class RouteListService extends IncyclistService {
66
66
  onCarouselInitialized(list: CardList<Route>, item: any, itemsInSlide: any): void;
67
67
  onCarouselUpdated(list: any, item: any, itemsInSlide: any): void;
68
68
  preload(): PromiseObserver<void>;
69
+ private createRoutesLogEntry;
69
70
  getLists(forUi?: boolean): Array<CardList<Route>>;
70
71
  getRouteDetails(id: string, expectLocal?: boolean): Promise<RouteApiDetail>;
71
72
  getSelectedRouteDetails(): Promise<RouteApiDetail>;
@@ -74,7 +75,7 @@ export declare class RouteListService extends IncyclistService {
74
75
  selectCard(card: Card<Route>): void;
75
76
  unselectCard(card: Card<Route>): void;
76
77
  import(info: FileInfo | Array<FileInfo>, retry?: ActiveImportCard): void;
77
- emitLists(event: 'loaded' | 'updated'): void;
78
+ emitLists(event: 'loaded' | 'updated', log?: boolean): void;
78
79
  getCard(id: string): RouteCard;
79
80
  addCardAgain(card: RouteCard): void;
80
81
  protected addImportCard(file: FileInfo): ActiveImportCard;
@@ -294,15 +294,10 @@ let RouteListService = (() => {
294
294
  this.preloadObserver = new observer_1.PromiseObserver(promise);
295
295
  this.preloadObserver.start()
296
296
  .then(() => {
297
- const cards = {
298
- myRoutes: this.myRoutes.length,
299
- selected: this.selectedRoutes.length,
300
- alternatives: this.alternatives.length
301
- };
302
- this.logEvent({ message: 'preload route list completed', cards });
297
+ this.logEvent({ message: 'preload route list completed' });
303
298
  this.initialized = true;
304
299
  (0, utils_1.updateRepoStats)();
305
- this.emitLists('loaded');
300
+ this.emitLists('loaded', true);
306
301
  process.nextTick(() => { delete this.preloadObserver; });
307
302
  })
308
303
  .catch((err) => {
@@ -315,6 +310,28 @@ let RouteListService = (() => {
315
310
  }
316
311
  return this.preloadObserver;
317
312
  }
313
+ createRoutesLogEntry(includeDetails = false) {
314
+ try {
315
+ const log = {
316
+ counts: {
317
+ myRoutes: this.myRoutes.length,
318
+ selected: this.selectedRoutes.length,
319
+ alternatives: this.alternatives.length
320
+ }
321
+ };
322
+ if (includeDetails) {
323
+ log.titles = {};
324
+ const getTitleLog = (list) => { var _a; return (_a = list === null || list === void 0 ? void 0 : list.getCards()) === null || _a === void 0 ? void 0 : _a.map(r => r.getTitle()).join(','); };
325
+ log.titles.myRoutes = getTitleLog(this.myRoutes);
326
+ log.titles.selected = getTitleLog(this.selectedRoutes);
327
+ log.titles.alternaties = getTitleLog(this.alternatives);
328
+ }
329
+ return log;
330
+ }
331
+ catch (err) {
332
+ this.logError(err, 'createRoutesLogEntry');
333
+ }
334
+ }
318
335
  getLists(forUi = true) {
319
336
  var _a, _b;
320
337
  try {
@@ -398,11 +415,15 @@ let RouteListService = (() => {
398
415
  importCards.push(retry);
399
416
  }
400
417
  files.forEach((file, idx) => __awaiter(this, void 0, void 0, function* () {
418
+ var _a, _b;
401
419
  if (!file)
402
420
  return;
403
421
  const importCard = importCards[idx];
422
+ const name = (_b = (_a = file.url) !== null && _a !== void 0 ? _a : file.filename) !== null && _b !== void 0 ? _b : file.name;
404
423
  try {
424
+ this.logEvent({ message: 'start import', name });
405
425
  const { data, details } = yield parsers_1.RouteParser.parse(file);
426
+ this.logEvent({ message: 'import completed', name });
406
427
  const route = new route_1.Route(data, details);
407
428
  route.description.tsImported = Date.now();
408
429
  const existing = this.findCard(route);
@@ -419,10 +440,11 @@ let RouteListService = (() => {
419
440
  this.myRoutes.add(card, true);
420
441
  this.myRoutes.remove(importCard);
421
442
  card.enableDelete(true);
422
- this.emitLists('updated');
443
+ this.emitLists('updated', true);
423
444
  this.verifyPoints(card, route);
424
445
  }
425
446
  catch (err) {
447
+ this.logEvent({ message: 'import failed', name, reason: err.message, stack: err.stack });
426
448
  importCard.setError(err);
427
449
  }
428
450
  }));
@@ -431,12 +453,17 @@ let RouteListService = (() => {
431
453
  this.logError(err, 'import', info);
432
454
  }
433
455
  }
434
- emitLists(event) {
456
+ emitLists(event, log = false) {
457
+ var _a;
435
458
  try {
436
459
  const lists = this.getLists();
437
460
  const hash = lists ? lists.map(l => l.getCards().map(c => c.getId()).join(',')).join(':') : '';
438
461
  if (this.observer)
439
462
  this.observer.emit(event, lists, hash);
463
+ if (log) {
464
+ const logs = (_a = this.createRoutesLogEntry(true)) !== null && _a !== void 0 ? _a : {};
465
+ this.logEvent(Object.assign({ message: `RoutesList ${event}` }, logs));
466
+ }
440
467
  }
441
468
  catch (err) {
442
469
  this.logError(err, 'emitLists', event);
@@ -452,7 +479,7 @@ let RouteListService = (() => {
452
479
  list.add(card);
453
480
  card.enableDelete(list.getId() === 'myRoutes');
454
481
  card.setList(list);
455
- this.emitLists('updated');
482
+ this.emitLists('updated', true);
456
483
  }
457
484
  addImportCard(file) {
458
485
  const card = new ActiveImportCard_1.ActiveImportCard(file);
@@ -46,3 +46,7 @@ export interface RoutesRepoUpdates {
46
46
  current?: number;
47
47
  initial?: number;
48
48
  }
49
+ export interface RouteListLog {
50
+ counts: Record<string, number>;
51
+ titles?: Record<string, string>;
52
+ }
@@ -8,6 +8,7 @@ export declare class BaseCard implements Card<Workout> {
8
8
  reset(): void;
9
9
  delete(): PromiseObserver<boolean>;
10
10
  getId(): string;
11
+ getTitle(): string;
11
12
  getData(): Workout;
12
13
  setData(_data: Workout): void;
13
14
  getCardType(): void;
@@ -18,6 +18,9 @@ class BaseCard {
18
18
  getId() {
19
19
  throw new Error("Method not implemented.");
20
20
  }
21
+ getTitle() {
22
+ throw new Error("Method not implemented.");
23
+ }
21
24
  getData() {
22
25
  throw new Error("Method not implemented.");
23
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.3.28",
3
+ "version": "1.3.30",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.26"
6
6
  },