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 service.connectToWorker = function() {
24 if (service.worker) return;
27 // relative path would be better...
28 service.worker = new SharedWorker(service.workerUrl);
30 console.error('SharedWorker() not supported', E);
31 service.cannotConnect = true;
35 service.worker.onerror = function(err) {
36 console.error('Error loading shared worker', err);
37 service.cannotConnect = true;
40 // List for responses and resolve the matching pending request.
41 service.worker.port.addEventListener('message', function(evt) {
42 var response = evt.data;
43 var reqId = response.id;
44 var req = service.pendingRequests.filter(
45 function(r) { return r.id === reqId})[0];
48 console.error('Recieved response for unknown request ' + reqId);
52 if (response.status === 'OK') {
53 req.deferred.resolve(response.result);
55 console.error('worker request failed with ' + response.error);
56 req.deferred.reject(response.error);
60 service.worker.port.start();
63 service.connectToSchemas = function() {
65 if (service.cannotConnect) {
66 // This can happen in certain environments
70 service.connectToWorker(); // no-op if already connected
74 service.activeSchemas.forEach(function(schema) {
75 promises.push(service.connectToSchema(schema));
78 return $q.all(promises).then(
80 function() {service.cannotConnect = true}
84 // Connects if necessary to the active schemas then relays the request.
85 service.request = function(args) {
86 return service.connectToSchemas().then(
88 return service.relayRequest(args);
93 // Send a request to the web worker and register the request for
95 // Store the request ID in the request arguments, so it's included
96 // in the response, and in the pendingRequests list for linking.
97 service.relayRequest = function(args) {
98 var deferred = $q.defer();
99 var reqId = service.autoId++;
101 service.pendingRequests.push({id : reqId, deferred: deferred});
102 service.worker.port.postMessage(args);
103 return deferred.promise;
106 // Create and connect to the give schema
107 service.connectToSchema = function(schema) {
109 if (service.connectedSchemas.includes(schema)) {
114 if (service.schemasInProgress[schema]) {
115 return service.schemasInProgress[schema];
118 var deferred = $q.defer();
120 service.relayRequest(
121 {schema: schema, action: 'createSchema'})
124 return service.relayRequest(
125 {schema: schema, action: 'connect'});
130 service.connectedSchemas.push(schema);
131 delete service.schemasInProgress[schema];
137 return service.schemasInProgress[schema] = deferred.promise;
140 service.isCacheGood = function (type) {
141 return service.request({
144 action: 'selectWhereEqual',
150 if (!row) { return false; }
151 // hard-coded 1 day offline cache timeout
152 return (new Date().getTime() - row.cachedate.getTime()) <= 86400000;
157 service.destroyPendingOfflineXacts = function () {
158 return service.request({
160 table: 'OfflineXact',
165 service.havePendingOfflineXacts = function () {
166 return service.request({
168 table: 'OfflineXact',
173 service.retrievePendingOfflineXacts = function () {
174 return service.request({
176 table: 'OfflineXact',
178 }).then(function(resp) {
179 return resp.map(function(x) { return x.value });
183 service.populateBlockList = function() {
184 return service.request({
185 action: 'populateBlockList',
186 authtoken: egCore.auth.token()
190 // Returns a promise with true for blocked, false for not blocked
191 service.testOfflineBlock = function (barcode) {
192 return service.request({
194 table: 'OfflineBlocks',
195 action: 'selectWhereEqual',
198 }).then(function(resp) {
199 if (resp.length === 0) return null;
200 return resp[0].reason;
204 service.addOfflineXact = function (obj) {
205 return service.request({
207 table: 'OfflineXact',
208 action: 'insertOrReplace',
213 service.setStatCatsCache = function (statcats) {
214 if (lf.isOffline || !statcats || statcats.length === 0)
217 var rows = statcats.map(function(cat) {
218 return {id: cat.id(), value: egCore.idl.toHash(cat)}
221 return service.request({
224 action: 'insertOrReplace',
229 service.getStatCatsCache = function () {
231 return service.request({
235 }).then(function(list) {
237 list.forEach(function(s) {
238 var sc = egCore.idl.fromHash('actsc', s.value);
240 if (Array.isArray(sc.default_entries())) {
242 sc.default_entries().map( function (k) {
243 return egCore.idl.fromHash('actsced', k);
248 if (Array.isArray(sc.entries())) {
250 sc.entries().map( function (k) {
251 return egCore.idl.fromHash('actsce', k);
263 service.setSettingsCache = function (settings) {
264 if (lf.isOffline) return $q.when();
267 angular.forEach(settings, function (val, key) {
268 rows.push({name : key, value : JSON.stringify(val)});
271 return service.request({
274 action: 'insertOrReplace',
279 service.getSettingsCache = function (settings) {
283 if (settings && settings.length) {
284 promise = service.request({
287 action: 'selectWhereIn',
292 promise = service.request({
301 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
307 service.setListInOfflineCache = function (type, list) {
308 if (lf.isOffline) return $q.when();
310 return service.isCacheGood(type).then(function(good) {
311 if (good) { return }; // already cached
313 var pkey = egCore.idl.classes[type].pkey;
314 var rows = Object.values(list).map(function(item) {
317 id: '' + item[pkey](),
318 object: egCore.idl.toHash(item)
322 return service.request({
325 action: 'insertOrReplace',
327 }).then(function(resp) {
328 return service.request({
331 action: 'insertOrReplace',
332 rows: [{type: type, cachedate : new Date()}]
338 service.getListFromOfflineCache = function(type) {
339 return service.request({
342 action: 'selectWhereEqual',
345 }).then(function(resp) {
346 return resp.map(function(item) {
347 return egCore.idl.fromHash(type,item['object']);
352 service.reconstituteList = function(type) {
354 console.debug('egLovefield reading ' + type + ' list');
355 return service.getListFromOfflineCache(type).then(function (list) {
356 egCore.env.absorbList(list, type, true)
357 return $q.when(true);
360 return $q.when(false);
363 service.reconstituteTree = function(type) {
365 console.debug('egLovefield reading ' + type + ' tree');
367 var pkey = egCore.idl.classes[type].pkey;
368 var parent_field = 'parent';
371 parent_field = 'parent_ou';
374 return service.getListFromOfflineCache(type).then(function (list) {
377 angular.forEach(list, function (item) {
379 // Special case for aou, to reconstitue ou_type
381 if (item.ou_type()) {
382 item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
386 hash[''+item[pkey]()] = item;
387 if (!item[parent_field]()) {
389 } else if (angular.isObject(item[parent_field]())) {
390 // un-objectify the parent
392 item[parent_field]()[pkey]()
397 angular.forEach(list, function (item) {
398 item.children([]); // just clear it out if there's junk in there
400 item.children( list.filter(function (kid) {
401 return kid[parent_field]() == item[pkey]();
405 angular.forEach(list, function (item) {
406 if (item[parent_field]()) {
407 item[parent_field]( hash[''+item[parent_field]()] );
411 egCore.env.absorbTree(top, type, true)
415 return $q.when(false);