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