changeset 324:e433a2da0ada

Refactor the import library logic slightly to waterfall the loading of the libraries and list requests, and send one response when all libraries have been loaded.
author Lucas Thompson <dev@lucas.im>
date Tue, 16 May 2017 16:16:57 +0100
parents 72673c954216
children 38f3fe548ac0
files src/app/feature-extraction-menu/feature-extraction-menu.component.ts src/app/services/feature-extraction/FeatureExtractionWorker.ts src/app/services/feature-extraction/feature-extraction.service.ts
diffstat 3 files changed, 79 insertions(+), 69 deletions(-) [+]
line wrap: on
line diff
--- a/src/app/feature-extraction-menu/feature-extraction-menu.component.ts	Tue May 16 11:15:43 2017 +0100
+++ b/src/app/feature-extraction-menu/feature-extraction-menu.component.ts	Tue May 16 16:16:57 2017 +0100
@@ -80,12 +80,9 @@
   }
 
   ngOnInit() {
-    this.piperService.list().then(this.populateExtractors).then(() => {
-      this.piperService.load('pyin');
-      // this.piperService.load('nnls-chroma');
-    });
     this.librariesUpdatedSubscription =
       this.piperService.librariesUpdated$.subscribe(this.populateExtractors);
+    this.piperService.list().then(this.populateExtractors);
   }
 
   extract(combinedKey: string): void {
@@ -97,9 +94,7 @@
   }
 
   load(): void {
-    this.piperService.updateAvailableLibraries().subscribe(res => {
-      Object.keys(res).forEach(key => this.piperService.load(key));
-    });
+    this.piperService.updateAvailableLibraries();
   }
 
   ngOnDestroy(): void {
--- a/src/app/services/feature-extraction/FeatureExtractionWorker.ts	Tue May 16 11:15:43 2017 +0100
+++ b/src/app/services/feature-extraction/FeatureExtractionWorker.ts	Tue May 16 16:16:57 2017 +0100
@@ -2,7 +2,7 @@
  * Created by lucas on 01/12/2016.
  */
 
-import {PiperVampService, ListRequest, ListResponse, Service} from 'piper';
+import {PiperVampService, ListRequest, ListResponse} from 'piper';
 import {
   SimpleRequest
 } from 'piper/HigherLevelUtilities';
@@ -33,6 +33,21 @@
 type RequireJs = (libs: string[], callback: (...libs: any[]) => void) => void;
 type Factory<T> = () => T;
 
+function waterfall<T>(tasks: (() => Promise<T>)[]): Promise<T[]> {
+  const reducer = (running: T[], next: Promise<T>): Promise<T[]> => {
+    return next.then(response => {
+      running = running.concat(response);
+      return running;
+    });
+  };
+
+  return tasks.reduce((runningResponses, nextResponse) => {
+    return runningResponses.then(response => {
+      return reducer(response, nextResponse());
+    })
+  }, Promise.resolve([]));
+}
+
 class AggregateStreamingService implements StreamingService {
   private services: Map<LibraryKey, Factory<PiperStreamingService>>;
 
@@ -50,26 +65,21 @@
     this.services.set(key, service);
   }
 
+  hasRemoteService(key: LibraryKey): boolean {
+    return this.services.has(key);
+  }
+
   list(request: ListRequest): Promise<ListResponse> {
-    //TODO refactor
     const listThunks: (() => Promise<ListResponse>)[] = [
       ...this.services.values()
     ].map(client => () => client().list({}));
 
-    const concatAvailable = (running: ListResponse,
-                             nextResponse: Promise<ListResponse>)
-      : Promise<ListResponse> => {
-      return nextResponse.then(response => {
-        running.available = running.available.concat(response.available);
-        return running;
-      });
-    };
-
-    return listThunks.reduce((runningResponses, nextResponse) => {
-      return runningResponses.then(response => {
-        return concatAvailable(response, nextResponse());
-      })
-    }, Promise.resolve({available: []}));
+    return waterfall(listThunks).then(responses => {
+      return responses.reduce((allAvailable, res) => {
+        allAvailable.available = allAvailable.available.concat(res.available);
+        return allAvailable;
+      }, {available: []});
+    })
   }
 
   process(request: SimpleRequest): Observable<StreamingResponse> {
@@ -124,10 +134,7 @@
   constructor(workerScope: DedicatedWorkerGlobalScope,
               private requireJs: RequireJs) {
     this.workerScope = workerScope;
-    this.remoteLibraries = new Map<LibraryKey, LibraryUri>([
-      // ['nnls-chroma', 'assets/extractors/NNLSChroma.js'],
-      ['pyin', 'assets/extractors/PYin.umd.js'],
-    ]);
+    this.remoteLibraries = new Map<LibraryKey, LibraryUri>();
     this.service = new ThrottledReducingAggregateService();
     this.setupImportLibraryListener();
     this.server = new WebWorkerStreamingServer(
@@ -139,40 +146,49 @@
   private setupImportLibraryListener(): void {
 
     this.workerScope.onmessage = (ev: MessageEvent) => {
-      const sendResponse = (result) => {
-        console.warn(ev.data.method, ev.data);
-        this.workerScope.postMessage({
-          method: ev.data.method,
-          result: result
-        });
-      };
       switch (ev.data.method) {
-        case 'import':
-          const key: LibraryKey = ev.data.params;
-          if (this.remoteLibraries.has(key)) {
-            this.requireJs([this.remoteLibraries.get(key)], (plugin) => {
-
-              const service = () => {
-                // TODO a factory with more logic probably belongs in piper-js
-                const lib: any | EmscriptenModule = plugin.createLibrary();
-                const isEmscriptenModule = typeof lib.cwrap === 'function';
-                return new PiperStreamingService(
-                  isEmscriptenModule ? new PiperVampService(lib) : lib // TODO
-                );
-              };
-              this.service.addService(key, service);
-              this.service.list({}).then(sendResponse);
-            });
-          } else {
-            console.error('Non registered library key.'); // TODO handle error
-          }
-          break;
         case 'addRemoteLibraries': // TODO rename
           const available: AvailableLibraries = ev.data.params;
-          Object.keys(available).forEach(libraryKey => {
-            this.remoteLibraries.set(libraryKey, available[libraryKey]);
+          const importThunks = Object.keys(available).map(libraryKey => {
+            return () => {
+              this.remoteLibraries.set(libraryKey, available[libraryKey]);
+              return this.import(libraryKey).then(key => {
+                return key;
+              });
+            };
           });
+          waterfall(importThunks).then(() => {
+            this.service.list({}).then(response => {
+              this.workerScope.postMessage({
+                method: 'import',
+                result: response
+              });
+            });
+          })
       }
     };
   }
+
+  private import(key: LibraryKey): Promise<LibraryKey> { // TODO return type?
+    return new Promise((res, rej) => {
+      if (this.remoteLibraries.has(key)) {
+        // TODO RequireJs can fail... need to reject the promise then
+        this.requireJs([this.remoteLibraries.get(key)], (plugin) => {
+
+          const service = () => {
+            // TODO a factory with more logic probably belongs in piper-js
+            const lib: any | EmscriptenModule = plugin.createLibrary();
+            const isEmscriptenModule = typeof lib.cwrap === 'function';
+            return new PiperStreamingService(
+              isEmscriptenModule ? new PiperVampService(lib) : lib // TODO
+            );
+          };
+          this.service.addService(key, service);
+          res(key);
+        });
+      } else {
+        rej('Invalid remote library key');
+      }
+    });
+  }
 }
--- a/src/app/services/feature-extraction/feature-extraction.service.ts	Tue May 16 11:15:43 2017 +0100
+++ b/src/app/services/feature-extraction/feature-extraction.service.ts	Tue May 16 16:16:57 2017 +0100
@@ -8,7 +8,7 @@
 } from 'piper/HigherLevelUtilities';
 import {Subject} from 'rxjs/Subject';
 import {Observable} from 'rxjs/Observable';
-import {Http, Response} from '@angular/http';
+import {Http} from '@angular/http';
 import {
   countingIdProvider,
   WebWorkerStreamingClient
@@ -63,7 +63,10 @@
   }
 
   list(): Promise<ListResponse> {
-    return this.client.list({});
+    return this.client.list({}).then(response => {
+      this.librariesUpdated.next(response);
+      return response;
+    });
   }
 
   extract(analysisItemId: string, request: SimpleRequest): Promise<void> {
@@ -87,20 +90,16 @@
     });
   }
 
-  updateAvailableLibraries(): Observable<AvailableLibraries> {
-    return this.http.get(this.repositoryUri)
-      .map(res => {
-        const map = res.json();
+  updateAvailableLibraries(): void {
+    this.http.get(this.repositoryUri)
+      .toPromise() // just turn into a promise for now to subscribe / execute
+      .then(res => {
         this.worker.postMessage({
           method: 'addRemoteLibraries',
-          params: map
-        });
-        return map;
+          params: res.json()
+        })
       })
-      .catch((error: Response | any) => {
-        console.error(error);
-        return Observable.throw(error);
-      });
+      .catch(console.error); // TODO Report error to user
   }
 
   load(libraryKey: string): void {