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(/PhantomJS/)) {
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 // Returns a promise with true for blocked, false for not blocked
234 service.testOfflineBlock = function (barcode) {
235 return service.request({
237 table: 'OfflineBlocks',
238 action: 'selectWhereEqual',
241 }).then(function(resp) {
242 if (resp.length === 0) return null;
243 return resp[0].reason;
247 service.setStatCatsCache = function (statcats) {
248 if (lf.isOffline || !statcats ||
249 statcats.length === 0 || !service.connectToWorker()) {
253 var rows = statcats.map(function(cat) {
254 return {id: cat.id(), value: egCore.idl.toHash(cat)}
257 return service.request({
260 action: 'insertOrReplace',
265 service.getStatCatsCache = function () {
266 return service.request({
270 }).then(function(list) {
272 list.forEach(function(s) {
273 var sc = egCore.idl.fromHash('actsc', s.value);
275 if (Array.isArray(sc.default_entries())) {
277 sc.default_entries().map( function (k) {
278 return egCore.idl.fromHash('actsced', k);
283 if (Array.isArray(sc.entries())) {
285 sc.entries().map( function (k) {
286 return egCore.idl.fromHash('actsce', k);
298 service.setSettingsCache = function (settings) {
299 if (lf.isOffline || !service.connectToWorker()) return $q.when();
302 angular.forEach(settings, function (val, key) {
303 rows.push({name : key, value : JSON.stringify(val)});
306 return service.request({
309 action: 'insertOrReplace',
314 service.getSettingsCache = function (settings) {
315 if (lf.isOffline || !service.connectToWorker()) return $q.when([]);
319 if (settings && settings.length) {
320 promise = service.request({
323 action: 'selectWhereIn',
328 promise = service.request({
337 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
343 service.destroySettingsCache = function () {
344 if (lf.isOffline || !service.connectToWorker()) return $q.when();
345 return service.request({
352 service.setListInOfflineCache = function (type, list) {
353 if (lf.isOffline || !service.connectToWorker()) return $q.when();
355 return service.isCacheGood(type).then(function(good) {
356 if (good) { return }; // already cached
358 var pkey = egCore.idl.classes[type].pkey;
359 var rows = Object.values(list).map(function(item) {
362 id: '' + item[pkey](),
363 object: egCore.idl.toHash(item)
367 return service.request({
370 action: 'insertOrReplace',
372 }).then(function(resp) {
373 return service.request({
376 action: 'insertOrReplace',
377 rows: [{type: type, cachedate : new Date()}]
383 service.getListFromOfflineCache = function(type) {
384 return service.request({
387 action: 'selectWhereEqual',
390 }).then(function(resp) {
391 return resp.map(function(item) {
392 return egCore.idl.fromHash(type,item['object']);
397 service.reconstituteList = function(type) {
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);
405 return $q.when(false);
408 service.reconstituteTree = function(type) {
410 console.debug('egLovefield reading ' + type + ' tree');
412 var pkey = egCore.idl.classes[type].pkey;
413 var parent_field = 'parent';
416 parent_field = 'parent_ou';
419 return service.getListFromOfflineCache(type).then(function (list) {
422 angular.forEach(list, function (item) {
424 // Special case for aou, to reconstitue ou_type
426 if (item.ou_type()) {
427 item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
431 hash[''+item[pkey]()] = item;
432 if (!item[parent_field]()) {
434 } else if (angular.isObject(item[parent_field]())) {
435 // un-objectify the parent
437 item[parent_field]()[pkey]()
442 angular.forEach(list, function (item) {
443 item.children([]); // just clear it out if there's junk in there
445 item.children( list.filter(function (kid) {
446 return kid[parent_field]() == item[pkey]();
450 angular.forEach(list, function (item) {
451 if (item[parent_field]()) {
452 item[parent_field]( hash[''+item[parent_field]()] );
457 // Sort the org tree before absorbing
458 egCore.env.sort_aou(top);
461 egCore.env.absorbTree(top, type, true)
465 return $q.when(false);