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