]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/services/lovefield.js
LP1901760 Remove SharedWorker testing cruft
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / services / lovefield.js
1 /**
2  * Core Service - egLovefield
3  *
4  * Lovefield wrapper factory for low level offline stuff
5  *
6  */
7 angular.module('egCoreMod')
8
9 .factory('egLovefield', ['$q','$rootScope','egCore','$timeout', 
10                  function($q , $rootScope , egCore , $timeout) { 
11
12     var service = {
13         autoId: 0, // each request gets a unique id.
14         cannotConnect: false,
15         pendingRequests: [],
16         activeSchemas: ['cache'], // add 'offline' in the offline UI
17         schemasInProgress: {},
18         connectedSchemas: [],
19         // TODO: relative path would be more portable
20         workerUrl: '/js/ui/default/staff/offline-db-worker.js'
21     };
22
23     // Returns true if the connection was possible
24     service.connectToWorker = function() {
25         if (service.worker) return true;
26         if (service.cannotConnect) return false;
27
28         try {
29             // relative path would be better...
30             service.worker = new SharedWorker(service.workerUrl);
31         } catch (E) {
32             console.warn('SharedWorker() not supported', E);
33             service.cannotConnect = true;
34             return false;
35         }
36
37         service.worker.onerror = function(err) {
38             // avoid spamming unit test runner on failure to connect.
39             if (!navigator.userAgent.match(/PhantomJS/)) {
40                 console.error('Error loading shared worker', err);
41             }
42             service.cannotConnect = true;
43         }
44
45         // List for responses and resolve the matching pending request.
46         service.worker.port.addEventListener('message', function(evt) {
47             var response = evt.data;
48             var reqId = response.id;
49             var req = service.pendingRequests.filter(
50                 function(r) { return r.id === reqId})[0];
51
52             if (!req) {
53                 console.error('Recieved response for unknown request ' + reqId);
54                 return;
55             }
56
57             if (response.status === 'OK') {
58                 req.deferred.resolve(response.result);
59             } else {
60                 console.error('worker request failed with ' + response.error);
61                 req.deferred.reject(response.error);
62             }
63         });
64
65         service.worker.port.start();
66         return true;
67     }
68
69     service.connectToSchemas = function() {
70
71         service.connectToWorker(); // no-op if already connected
72
73         if (service.cannotConnect) { 
74             // This can happen in certain environments
75             return $q.reject();
76         }
77
78         var promises = [];
79
80         service.activeSchemas.forEach(function(schema) {
81             promises.push(service.connectToSchema(schema));
82         });
83
84         return $q.all(promises).then(
85             function() {},
86             function() {service.cannotConnect = true}
87         );
88     }
89
90     // Connects if necessary to the active schemas then relays the request.
91     service.request = function(args) {
92         // useful, but very chatty, leaving commented out.
93         // console.debug('egLovfield sending request: ', args);
94         return service.connectToSchemas().then(
95             function() {
96                 return service.relayRequest(args);
97             }
98         );
99     }
100
101     // Send a request to the web worker and register the request for
102     // future resolution.
103     // Store the request ID in the request arguments, so it's included
104     // in the response, and in the pendingRequests list for linking.
105     service.relayRequest = function(args) {
106         var deferred = $q.defer();
107         var reqId = service.autoId++;
108         args.id = reqId;
109         service.pendingRequests.push({id : reqId, deferred: deferred});
110         service.worker.port.postMessage(args);
111         return deferred.promise;
112     }
113
114     // Create and connect to the give schema
115     service.connectToSchema = function(schema) {
116
117         if (service.connectedSchemas.indexOf(schema) >= 0) {
118             // already connected
119             return $q.when();
120         }
121
122         if (service.schemasInProgress[schema]) {
123             return service.schemasInProgress[schema];
124         }
125
126         var deferred = $q.defer();
127
128         service.relayRequest(
129             {schema: schema, action: 'createSchema'}) 
130         .then(
131             function() {
132                 return service.relayRequest(
133                     {schema: schema, action: 'connect'});
134             },
135             deferred.reject
136         ).then(
137             function() { 
138                 service.connectedSchemas.push(schema); 
139                 delete service.schemasInProgress[schema];
140                 deferred.resolve();
141             },
142             deferred.reject
143         );
144
145         return service.schemasInProgress[schema] = deferred.promise;
146     }
147
148     service.isCacheGood = function (type) {
149         if (lf.isOffline || !service.connectToWorker()) return $q.when(true);
150
151         return service.request({
152             schema: 'cache',
153             table: 'CacheDate',
154             action: 'selectWhereEqual',
155             field: 'type',
156             value: type
157         }).then(
158             function(result) {
159                 var row = result[0];
160                 if (!row) { return false; }
161                 // hard-coded 1 day offline cache timeout
162                 return (new Date().getTime() - row.cachedate.getTime()) <= 86400000;
163             }
164         );
165     }
166
167     // Remove all pending offline transactions and delete the cached
168     // offline transactions date to indicate no transactions remain.
169     service.destroyPendingOfflineXacts = function () {
170         return service.request({
171             schema: 'offline',
172             table: 'OfflineXact',
173             action: 'deleteAll'
174         }).then(function() {
175             return service.request({
176                 schema: 'cache',
177                 table: 'CacheDate',
178                 action: 'deleteWhereEqual',
179                 field: 'type',
180                 value: '_offlineXact'
181             });
182         });
183     }
184
185     // Returns the cache date when xacts exit, null otherwise.
186     service.havePendingOfflineXacts = function () {
187         return service.request({
188             schema: 'cache',
189             table: 'CacheDate',
190             action: 'selectWhereEqual',
191             field: 'type',
192             value: '_offlineXact'
193         }).then(function(results) {
194             return results[0] ? results[0].cachedate : null;
195         });
196     }
197
198     service.retrievePendingOfflineXacts = function () {
199         return service.request({
200             schema: 'offline',
201             table: 'OfflineXact',
202             action: 'selectAll'
203         }).then(function(resp) {
204             return resp.map(function(x) { return x.value });
205         });
206     }
207
208     // Add an offline transaction and update the cache indicating
209     // now() as the most recent addition of an offline xact.
210     service.addOfflineXact = function (obj) {
211         return service.request({
212             schema: 'offline',
213             table: 'OfflineXact',
214             action: 'insertOrReplace',
215             rows: [{value: obj}]
216         }).then(function() {
217             return service.request({
218                 schema: 'cache',
219                 table: 'CacheDate',
220                 action: 'insertOrReplace',
221                 rows: [{type: '_offlineXact', cachedate : new Date()}]
222             });
223         });
224     }
225
226     service.populateBlockList = function() {
227         return service.request({
228             action: 'populateBlockList',
229             authtoken: egCore.auth.token()
230         });
231     }
232
233     // Returns a promise with true for blocked, false for not blocked
234     service.testOfflineBlock = function (barcode) {
235         return service.request({
236             schema: 'offline',
237             table: 'OfflineBlocks',
238             action: 'selectWhereEqual',
239             field: 'barcode',
240             value: barcode
241         }).then(function(resp) {
242             if (resp.length === 0) return null;
243             return resp[0].reason;
244         });
245     }
246
247     service.setStatCatsCache = function (statcats) {
248         if (lf.isOffline || !statcats || 
249             statcats.length === 0 || !service.connectToWorker()) {
250             return $q.when();
251         }
252
253         var rows = statcats.map(function(cat) {
254             return {id: cat.id(), value: egCore.idl.toHash(cat)}
255         });
256
257         return service.request({
258             schema: 'cache',
259             table: 'StatCat',
260             action: 'insertOrReplace',
261             rows: rows
262         });
263     }
264
265     service.getStatCatsCache = function () {
266         return service.request({
267             schema: 'cache',
268             table: 'StatCat',
269             action: 'selectAll'
270         }).then(function(list) {
271             var result = [];
272             list.forEach(function(s) {
273                 var sc = egCore.idl.fromHash('actsc', s.value);
274
275                 if (Array.isArray(sc.default_entries())) {
276                     sc.default_entries(
277                         sc.default_entries().map( function (k) {
278                             return egCore.idl.fromHash('actsced', k);
279                         })
280                     );
281                 }
282
283                 if (Array.isArray(sc.entries())) {
284                     sc.entries(
285                         sc.entries().map( function (k) {
286                             return egCore.idl.fromHash('actsce', k);
287                         })
288                     );
289                 }
290
291                 result.push(sc);
292             });
293
294             return result;
295         });
296     }
297
298     service.setSettingsCache = function (settings) {
299         if (lf.isOffline || !service.connectToWorker()) return $q.when();
300
301         var rows = [];
302         angular.forEach(settings, function (val, key) {
303             rows.push({name  : key, value : JSON.stringify(val)});
304         });
305
306         return service.request({
307             schema: 'cache',
308             table: 'Setting',
309             action: 'insertOrReplace',
310             rows: rows
311         });
312     }
313
314     service.getSettingsCache = function (settings) {
315         if (lf.isOffline || !service.connectToWorker()) return $q.when([]);
316
317         var promise;
318
319         if (settings && settings.length) {
320             promise = service.request({
321                 schema: 'cache',
322                 table: 'Setting',
323                 action: 'selectWhereIn',
324                 field: 'name',
325                 value: settings
326             });
327         } else {
328             promise = service.request({
329                 schema: 'cache',
330                 table: 'Setting',
331                 action: 'selectAll'
332             });
333         }
334
335         return promise.then(
336             function(resp) {
337                 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
338                 return resp;
339             }
340         );
341     }
342
343     service.destroySettingsCache = function () {
344         if (lf.isOffline || !service.connectToWorker()) return $q.when();
345         return service.request({
346             schema: 'cache',
347             table: 'Setting',
348             action: 'deleteAll'
349         });
350     }
351
352     service.setListInOfflineCache = function (type, list) {
353         if (lf.isOffline || !service.connectToWorker()) return $q.when();
354
355         return service.isCacheGood(type).then(function(good) {
356             if (good) { return };  // already cached
357
358             var pkey = egCore.idl.classes[type].pkey;
359             var rows = Object.values(list).map(function(item) {
360                 return {
361                     type: type, 
362                     id: '' + item[pkey](), 
363                     object: egCore.idl.toHash(item)
364                 };
365             });
366
367             return service.request({
368                 schema: 'cache',
369                 table: 'Object',
370                 action: 'insertOrReplace',
371                 rows: rows
372             }).then(function(resp) {
373                 return service.request({
374                     schema: 'cache',
375                     table: 'CacheDate',
376                     action: 'insertOrReplace',
377                     rows: [{type: type, cachedate : new Date()}]
378                 });
379             });
380         });
381     }
382
383     service.getListFromOfflineCache = function(type) {
384         return service.request({
385             schema: 'cache',
386             table: 'Object',
387             action: 'selectWhereEqual',
388             field: 'type',
389             value: type
390         }).then(function(resp) {
391             return resp.map(function(item) {
392                 return egCore.idl.fromHash(type,item['object']);
393             });
394         });
395     }
396
397     service.reconstituteList = function(type) {
398         if (lf.isOffline) {
399             console.debug('egLovefield reading ' + type + ' list');
400             return service.getListFromOfflineCache(type).then(function (list) {
401                 egCore.env.absorbList(list, type, true)
402                 return $q.when(true);
403             });
404         }
405         return $q.when(false);
406     }
407
408     service.reconstituteTree = function(type) {
409         if (lf.isOffline) {
410             console.debug('egLovefield reading ' + type + ' tree');
411
412             var pkey = egCore.idl.classes[type].pkey;
413             var parent_field = 'parent';
414
415             if (type == 'aou') {
416                 parent_field = 'parent_ou';
417             }
418
419             return service.getListFromOfflineCache(type).then(function (list) {
420                 var hash = {};
421                 var top = null;
422                 angular.forEach(list, function (item) {
423
424                     // Special case for aou, to reconstitue ou_type
425                     if (type == 'aou') {
426                         if (item.ou_type()) {
427                             item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
428                         }
429                     }
430
431                     hash[''+item[pkey]()] = item;
432                     if (!item[parent_field]()) {
433                         top = item;
434                     } else if (angular.isObject(item[parent_field]())) {
435                         // un-objectify the parent
436                         item[parent_field](
437                             item[parent_field]()[pkey]()
438                         );
439                     }
440                 });
441
442                 angular.forEach(list, function (item) {
443                     item.children([]); // just clear it out if there's junk in there
444
445                     item.children( list.filter(function (kid) {
446                         return kid[parent_field]() == item[pkey]();
447                     }) );
448                 });
449
450                 angular.forEach(list, function (item) {
451                     if (item[parent_field]()) {
452                         item[parent_field]( hash[''+item[parent_field]()] );
453                     }
454                 });
455
456                 if (type == 'aou') {
457                     // Sort the org tree before absorbing
458                     egCore.env.sort_aou(top);
459                 }
460
461                 egCore.env.absorbTree(top, type, true)
462                 return $q.when(true)
463             });
464         }
465         return $q.when(false);
466     }
467
468     return service;
469 }]);
470