]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/services/lovefield.js
LP2045292 Color contrast for AngularJS patron bills
[working/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(/Headless/)) {
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     service.setOfflineBlockDate = function () {
234         return service.request({
235             schema: 'cache',
236             table: 'CacheDate',
237             action: 'insertOrReplace',
238             rows: [{type: '_blocklistDownload', cachedate : new Date()}]
239         });
240     }
241
242     service.getOfflineBlockDate = function () {
243         return service.request({
244             schema: 'cache',
245             table: 'CacheDate',
246             action: 'selectWhereEqual',
247             field: 'type',
248             value: '_blocklistDownload'
249         }).then(function(results) {
250             return results[0] ? results[0].cachedate : null;
251         });
252     }
253
254     // Returns a promise with true for blocked, false for not blocked
255     service.testOfflineBlock = function (barcode) {
256         return service.request({
257             schema: 'offline',
258             table: 'OfflineBlocks',
259             action: 'selectWhereEqual',
260             field: 'barcode',
261             value: barcode
262         }).then(function(resp) {
263             if (resp.length === 0) return null;
264             return resp[0].reason;
265         });
266     }
267
268     service.setStatCatsCache = function (statcats) {
269         if (lf.isOffline || !statcats || 
270             statcats.length === 0 || !service.connectToWorker()) {
271             return $q.when();
272         }
273
274         var rows = statcats.map(function(cat) {
275             return {id: cat.id(), value: egCore.idl.toHash(cat)}
276         });
277
278         return service.request({
279             schema: 'cache',
280             table: 'StatCat',
281             action: 'insertOrReplace',
282             rows: rows
283         });
284     }
285
286     service.getStatCatsCache = function () {
287         return service.request({
288             schema: 'cache',
289             table: 'StatCat',
290             action: 'selectAll'
291         }).then(function(list) {
292             var result = [];
293             list.forEach(function(s) {
294                 var sc = egCore.idl.fromHash('actsc', s.value);
295
296                 if (Array.isArray(sc.default_entries())) {
297                     sc.default_entries(
298                         sc.default_entries().map( function (k) {
299                             return egCore.idl.fromHash('actsced', k);
300                         })
301                     );
302                 }
303
304                 if (Array.isArray(sc.entries())) {
305                     sc.entries(
306                         sc.entries().map( function (k) {
307                             return egCore.idl.fromHash('actsce', k);
308                         })
309                     );
310                 }
311
312                 result.push(sc);
313             });
314
315             return result;
316         });
317     }
318
319     service.setSettingsCache = function (settings) {
320         if (lf.isOffline || !service.connectToWorker()) return $q.when();
321
322         var rows = [];
323         angular.forEach(settings, function (val, key) {
324             rows.push({name  : key, value : JSON.stringify(val)});
325         });
326
327         return service.request({
328             schema: 'cache',
329             table: 'Setting',
330             action: 'insertOrReplace',
331             rows: rows
332         });
333     }
334
335     service.getSettingsCache = function (settings) {
336         if (lf.isOffline || !service.connectToWorker()) return $q.when([]);
337
338         var promise;
339
340         if (settings && settings.length) {
341             promise = service.request({
342                 schema: 'cache',
343                 table: 'Setting',
344                 action: 'selectWhereIn',
345                 field: 'name',
346                 value: settings
347             });
348         } else {
349             promise = service.request({
350                 schema: 'cache',
351                 table: 'Setting',
352                 action: 'selectAll'
353             });
354         }
355
356         return promise.then(
357             function(resp) {
358                 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
359                 return resp;
360             }
361         );
362     }
363
364     service.destroySettingsCache = function () {
365         if (lf.isOffline || !service.connectToWorker()) return $q.when();
366         return service.request({
367             schema: 'cache',
368             table: 'Setting',
369             action: 'deleteAll'
370         });
371     }
372
373     service.setListInOfflineCache = function (type, list) {
374         if (lf.isOffline || !service.connectToWorker()) return $q.when();
375
376         return service.isCacheGood(type).then(function(good) {
377             if (good) { return };  // already cached
378
379             var pkey = egCore.idl.classes[type].pkey;
380             var rows = Object.values(list).map(function(item) {
381                 return {
382                     type: type, 
383                     id: '' + item[pkey](), 
384                     object: egCore.idl.toHash(item)
385                 };
386             });
387
388             return service.request({
389                 schema: 'cache',
390                 table: 'Object',
391                 action: 'insertOrReplace',
392                 rows: rows
393             }).then(function(resp) {
394                 return service.request({
395                     schema: 'cache',
396                     table: 'CacheDate',
397                     action: 'insertOrReplace',
398                     rows: [{type: type, cachedate : new Date()}]
399                 });
400             });
401         });
402     }
403
404     service.getListFromOfflineCache = function(type) {
405         return service.request({
406             schema: 'cache',
407             table: 'Object',
408             action: 'selectWhereEqual',
409             field: 'type',
410             value: type
411         }).then(function(resp) {
412             return resp.map(function(item) {
413                 return egCore.idl.fromHash(type,item['object']);
414             });
415         });
416     }
417
418     service.reconstituteList = function(type) {
419         if (lf.isOffline) {
420             console.debug('egLovefield reading ' + type + ' list');
421             return service.getListFromOfflineCache(type).then(function (list) {
422                 egCore.env.absorbList(list, type, true)
423                 return $q.when(true);
424             });
425         }
426         return $q.when(false);
427     }
428
429     service.reconstituteTree = function(type) {
430         if (lf.isOffline) {
431             console.debug('egLovefield reading ' + type + ' tree');
432
433             var pkey = egCore.idl.classes[type].pkey;
434             var parent_field = 'parent';
435
436             if (type == 'aou') {
437                 parent_field = 'parent_ou';
438             }
439
440             return service.getListFromOfflineCache(type).then(function (list) {
441                 var hash = {};
442                 var top = null;
443                 angular.forEach(list, function (item) {
444
445                     // Special case for aou, to reconstitue ou_type
446                     if (type == 'aou') {
447                         if (item.ou_type()) {
448                             item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
449                         }
450                     }
451
452                     hash[''+item[pkey]()] = item;
453                     if (!item[parent_field]()) {
454                         top = item;
455                     } else if (angular.isObject(item[parent_field]())) {
456                         // un-objectify the parent
457                         item[parent_field](
458                             item[parent_field]()[pkey]()
459                         );
460                     }
461                 });
462
463                 angular.forEach(list, function (item) {
464                     item.children([]); // just clear it out if there's junk in there
465
466                     item.children( list.filter(function (kid) {
467                         return kid[parent_field]() == item[pkey]();
468                     }) );
469                 });
470
471                 angular.forEach(list, function (item) {
472                     if (item[parent_field]()) {
473                         item[parent_field]( hash[''+item[parent_field]()] );
474                     }
475                 });
476
477                 if (type == 'aou') {
478                     // Sort the org tree before absorbing
479                     egCore.env.sort_aou(top);
480                 }
481
482                 egCore.env.absorbTree(top, type, true)
483                 return $q.when(true)
484             });
485         }
486         return $q.when(false);
487     }
488
489     return service;
490 }]);
491