1/* 2 * Copyright 2024 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import { HttpClient, HttpHeaders } from '@angular/common/http'; 18import { Inject, Injectable, InjectionToken } from '@angular/core'; 19import { Observable, of } from 'rxjs'; 20import { catchError, filter, map, tap } from 'rxjs/operators'; 21 22import { MotionGolden, MotionGoldenData } from '../model/golden'; 23import { RecordedMotion } from '../model/recorded-motion'; 24import { Timeline } from '../model/timeline'; 25import { VideoSource } from '../model/video-source'; 26import { checkNotNull } from '../util/preconditions'; 27import { Feature, recordedFeatureFactory } from '../model/feature'; 28 29export const ACCESS_TOKEN = new InjectionToken<string>('token'); 30export const SERVICE_PORT = new InjectionToken<string>('port'); 31 32@Injectable({ providedIn: 'root' }) 33export class GoldensService { 34 private serverRoot: string; 35 private defaultHeaders: { [heder: string]: string }; 36 37 constructor( 38 private http: HttpClient, 39 @Inject(ACCESS_TOKEN) config: string, 40 @Inject(SERVICE_PORT) port: string 41 ) { 42 this.serverRoot = `http://localhost:${port}`; 43 this.defaultHeaders = { 44 'Golden-Access-Token': config, 45 }; 46 } 47 48 getGoldens(): Observable<MotionGolden[]> { 49 return this.http 50 .get<MotionGolden[]>(`${this.serverRoot}/service/list`, { 51 headers: this.defaultHeaders, 52 }) 53 .pipe( 54 tap((x) => console.log(`listed goldens, got ${x.length} results`)), 55 catchError(this.handleError<MotionGolden[]>('e')) 56 ); 57 } 58 59 loadRecordedMotion(golden: MotionGolden): Observable<RecordedMotion> { 60 const videoUrl = checkNotNull(golden.videoUrl); 61 return this.getActualGoldenData(golden).pipe( 62 map((data) => { 63 const timeline = new Timeline(data.frame_ids); 64 const videoSource = new VideoSource(videoUrl, timeline); 65 const features = data.features.map((it) => recordedFeatureFactory(it)); 66 67 return new RecordedMotion(videoSource, timeline, features); 68 }) 69 ); 70 } 71 72 getActualGoldenData(golden: MotionGolden): Observable<MotionGoldenData> { 73 return this.http 74 .get<MotionGoldenData>(`${golden.actualUrl}`, { 75 headers: this.defaultHeaders, 76 }) 77 .pipe( 78 tap((x) => console.log(`listed loaded golden data`)), 79 catchError(this.handleError<MotionGoldenData>('e')) 80 ); 81 } 82 83 getExpectedGoldenData(golden: MotionGolden): Observable<MotionGoldenData> { 84 return this.http 85 .get<MotionGoldenData>(`${golden.expectedUrl}`, { 86 headers: this.defaultHeaders, 87 }) 88 .pipe( 89 tap((x) => console.log('listed expected golden data')), 90 catchError(this.handleError<MotionGoldenData>('e')) 91 ); 92 } 93 94 refreshGoldens(clear: boolean): Observable<MotionGolden[]> { 95 return this.http 96 .post<MotionGolden[]>( 97 `${this.serverRoot}/service/refresh`, 98 { clear }, 99 { 100 headers: { 101 ...this.defaultHeaders, 102 'Content-Type': 'application/json', 103 }, 104 } 105 ) 106 .pipe( 107 tap((_) => console.log(`refreshed goldens (clear)`)), 108 catchError(this.handleError<MotionGolden[]>('e')) 109 ); 110 } 111 112 updateGolden(golden: MotionGolden): Observable<void> { 113 return this.http 114 .put<void>( 115 `${this.serverRoot}/service/update?id=${golden.id}`, 116 {}, 117 { headers: this.defaultHeaders } 118 ) 119 .pipe( 120 tap((_) => { 121 console.log(`updated golden`); 122 golden.updated = true; 123 }), 124 catchError(this.handleError<void>('update')) 125 ); 126 } 127 128 private handleError<T>(operation = 'operation', result?: T) { 129 return (error: any): Observable<T> => { 130 console.error(error); 131 132 // Let the app keep running by returning an empty result. 133 return of(result as T); 134 }; 135 } 136} 137