2 * Core Service - egLovefield
4 * Lovefield wrapper factory for low level offline stuff
7 angular.module('egCoreMod')
9 .factory('egLovefield', ['$q','$rootScope','egCore','$timeout',
10 function($q , $rootScope , egCore , $timeout) {
13 autoId: 0, // each request gets a unique id.
16 activeSchemas: ['cache'], // add 'offline' in the offline UI
17 schemasInProgress: {},
19 // TODO: relative path would be more portable
20 workerUrl: '/js/ui/default/staff/offline-db-worker.js'
23 // Returns true if the connection was possible
24 service.connectToWorker = function() {
25 if (service.worker) return true;
26 if (service.cannotConnect) return false;
29 // relative path would be better...
30 service.worker = new SharedWorker(service.workerUrl);
32 console.warn('SharedWorker() not supported', E);
33 service.cannotConnect = true;
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);
42 service.cannotConnect = true;
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];
53 console.error('Recieved response for unknown request ' + reqId);
57 if (response.status === 'OK') {
58 req.deferred.resolve(response.result);
60 console.error('worker request failed with ' + response.error);
61 req.deferred.reject(response.error);
65 service.worker.port.start();
69 service.connectToSchemas = function() {
71 service.connectToWorker(); // no-op if already connected
73 if (service.cannotConnect) {
74 // This can happen in certain environments
80 service.activeSchemas.forEach(function(schema) {
81 promises.push(service.connectToSchema(schema));
84 return $q.all(promises).then(
86 function() {service.cannotConnect = true}
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(
96 return service.relayRequest(args);
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++;
109 service.pendingRequests.push({id : reqId, deferred: deferred});
110 service.worker.port.postMessage(args);
111 return deferred.promise;
114 // Create and connect to the give schema
115 service.connectToSchema = function(schema) {
117 if (service.connectedSchemas.indexOf(schema) >= 0) {
122 if (service.schemasInProgress[schema]) {
123 return service.schemasInProgress[schema];
126 var deferred = $q.defer();
128 service.relayRequest(
129 {schema: schema, action: 'createSchema'})
132 return service.relayRequest(
133 {schema: schema, action: 'connect'});
138 service.connectedSchemas.push(schema);
139 delete service.schemasInProgress[schema];
145 return service.schemasInProgress[schema] = deferred.promise;
148 service.isCacheGood = function (type) {
149 if (lf.isOffline || !service.connectToWorker()) return $q.when(true);
151 return service.request({
154 action: 'selectWhereEqual',
160 if (!row) { return false; }
161 // hard-coded 1 day offline cache timeout
162 return (new Date().getTime() - row.cachedate.getTime()) <= 86400000;
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({
172 table: 'OfflineXact',
175 return service.request({
178 action: 'deleteWhereEqual',
180 value: '_offlineXact'
185 // Returns the cache date when xacts exit, null otherwise.
186 service.havePendingOfflineXacts = function () {
187 return service.request({
190 action: 'selectWhereEqual',
192 value: '_offlineXact'
193 }).then(function(results) {
194 return results[0] ? results[0].cachedate : null;
198 service.retrievePendingOfflineXacts = function () {
199 return service.request({
201 table: 'OfflineXact',
203 }).then(function(resp) {
204 return resp.map(function(x) { return x.value });
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({
213 table: 'OfflineXact',
214 action: 'insertOrReplace',
217 return service.request({
220 action: 'insertOrReplace',
221 rows: [{type: '_offlineXact', cachedate : new Date()}]
226 service.populateBlockList = function() {
227 return service.request({
228 action: 'populateBlockList',
229 authtoken: egCore.auth.token()
233 service.setOfflineBlockDate = function () {
234 return service.request({
237 action: 'insertOrReplace',
238 rows: [{type: '_blocklistDownload', cachedate : new Date()}]
242 service.getOfflineBlockDate = function () {
243 return service.request({
246 action: 'selectWhereEqual',
248 value: '_blocklistDownload'
249 }).then(function(results) {
250 return results[0] ? results[0].cachedate : null;
254 // Returns a promise with true for blocked, false for not blocked
255 service.testOfflineBlock = function (barcode) {
256 return service.request({
258 table: 'OfflineBlocks',
259 action: 'selectWhereEqual',
262 }).then(function(resp) {
263 if (resp.length === 0) return null;
264 return resp[0].reason;
268 service.setStatCatsCache = function (statcats) {
269 if (lf.isOffline || !statcats ||
270 statcats.length === 0 || !service.connectToWorker()) {
274 var rows = statcats.map(function(cat) {
275 return {id: cat.id(), value: egCore.idl.toHash(cat)}
278 return service.request({
281 action: 'insertOrReplace',
286 service.getStatCatsCache = function () {
287 return service.request({
291 }).then(function(list) {
293 list.forEach(function(s) {
294 var sc = egCore.idl.fromHash('actsc', s.value);
296 if (Array.isArray(sc.default_entries())) {
298 sc.default_entries().map( function (k) {
299 return egCore.idl.fromHash('actsced', k);
304 if (Array.isArray(sc.entries())) {
306 sc.entries().map( function (k) {
307 return egCore.idl.fromHash('actsce', k);
319 service.setSettingsCache = function (settings) {
320 if (lf.isOffline || !service.connectToWorker()) return $q.when();
323 angular.forEach(settings, function (val, key) {
324 rows.push({name : key, value : JSON.stringify(val)});
327 return service.request({
330 action: 'insertOrReplace',
335 service.getSettingsCache = function (settings) {
336 if (lf.isOffline || !service.connectToWorker()) return $q.when([]);
340 if (settings && settings.length) {
341 promise = service.request({
344 action: 'selectWhereIn',
349 promise = service.request({
358 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
364 service.destroySettingsCache = function () {
365 if (lf.isOffline || !service.connectToWorker()) return $q.when();
366 return service.request({
373 service.setListInOfflineCache = function (type, list) {
374 if (lf.isOffline || !service.connectToWorker()) return $q.when();
376 return service.isCacheGood(type).then(function(good) {
377 if (good) { return }; // already cached
379 var pkey = egCore.idl.classes[type].pkey;
380 var rows = Object.values(list).map(function(item) {
383 id: '' + item[pkey](),
384 object: egCore.idl.toHash(item)
388 return service.request({
391 action: 'insertOrReplace',
393 }).then(function(resp) {
394 return service.request({
397 action: 'insertOrReplace',
398 rows: [{type: type, cachedate : new Date()}]
404 service.getListFromOfflineCache = function(type) {
405 return service.request({
408 action: 'selectWhereEqual',
411 }).then(function(resp) {
412 return resp.map(function(item) {
413 return egCore.idl.fromHash(type,item['object']);
418 service.reconstituteList = function(type) {
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);
426 return $q.when(false);
429 service.reconstituteTree = function(type) {
431 console.debug('egLovefield reading ' + type + ' tree');
433 var pkey = egCore.idl.classes[type].pkey;
434 var parent_field = 'parent';
437 parent_field = 'parent_ou';
440 return service.getListFromOfflineCache(type).then(function (list) {
443 angular.forEach(list, function (item) {
445 // Special case for aou, to reconstitue ou_type
447 if (item.ou_type()) {
448 item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
452 hash[''+item[pkey]()] = item;
453 if (!item[parent_field]()) {
455 } else if (angular.isObject(item[parent_field]())) {
456 // un-objectify the parent
458 item[parent_field]()[pkey]()
463 angular.forEach(list, function (item) {
464 item.children([]); // just clear it out if there's junk in there
466 item.children( list.filter(function (kid) {
467 return kid[parent_field]() == item[pkey]();
471 angular.forEach(list, function (item) {
472 if (item[parent_field]()) {
473 item[parent_field]( hash[''+item[parent_field]()] );
478 // Sort the org tree before absorbing
479 egCore.env.sort_aou(top);
482 egCore.env.absorbTree(top, type, true)
486 return $q.when(false);