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 // useful, but very chatty, leaving commented out.
87 // console.debug('egLovfield sending request: ', args);
88 return service.connectToSchemas().then(
90 return service.relayRequest(args);
95 // Send a request to the web worker and register the request for
97 // Store the request ID in the request arguments, so it's included
98 // in the response, and in the pendingRequests list for linking.
99 service.relayRequest = function(args) {
100 var deferred = $q.defer();
101 var reqId = service.autoId++;
103 service.pendingRequests.push({id : reqId, deferred: deferred});
104 service.worker.port.postMessage(args);
105 return deferred.promise;
108 // Create and connect to the give schema
109 service.connectToSchema = function(schema) {
111 if (service.connectedSchemas.includes(schema)) {
116 if (service.schemasInProgress[schema]) {
117 return service.schemasInProgress[schema];
120 var deferred = $q.defer();
122 service.relayRequest(
123 {schema: schema, action: 'createSchema'})
126 return service.relayRequest(
127 {schema: schema, action: 'connect'});
132 service.connectedSchemas.push(schema);
133 delete service.schemasInProgress[schema];
139 return service.schemasInProgress[schema] = deferred.promise;
142 service.isCacheGood = function (type) {
143 return service.request({
146 action: 'selectWhereEqual',
152 if (!row) { return false; }
153 // hard-coded 1 day offline cache timeout
154 return (new Date().getTime() - row.cachedate.getTime()) <= 86400000;
159 // Remove all pending offline transactions and delete the cached
160 // offline transactions date to indicate no transactions remain.
161 service.destroyPendingOfflineXacts = function () {
162 return service.request({
164 table: 'OfflineXact',
167 return service.request({
170 action: 'deleteWhereEqual',
172 value: '_offlineXact'
177 // Returns the cache date when xacts exit, null otherwise.
178 service.havePendingOfflineXacts = function () {
179 return service.request({
182 action: 'selectWhereEqual',
184 value: '_offlineXact'
185 }).then(function(results) {
186 return results[0] ? results[0].cachedate : null;
190 service.retrievePendingOfflineXacts = function () {
191 return service.request({
193 table: 'OfflineXact',
195 }).then(function(resp) {
196 return resp.map(function(x) { return x.value });
200 // Add an offline transaction and update the cache indicating
201 // now() as the most recent addition of an offline xact.
202 service.addOfflineXact = function (obj) {
203 return service.request({
205 table: 'OfflineXact',
206 action: 'insertOrReplace',
209 return service.request({
212 action: 'insertOrReplace',
213 rows: [{type: '_offlineXact', cachedate : new Date()}]
218 service.populateBlockList = function() {
219 return service.request({
220 action: 'populateBlockList',
221 authtoken: egCore.auth.token()
225 // Returns a promise with true for blocked, false for not blocked
226 service.testOfflineBlock = function (barcode) {
227 return service.request({
229 table: 'OfflineBlocks',
230 action: 'selectWhereEqual',
233 }).then(function(resp) {
234 if (resp.length === 0) return null;
235 return resp[0].reason;
239 service.setStatCatsCache = function (statcats) {
240 if (lf.isOffline || !statcats || statcats.length === 0)
243 var rows = statcats.map(function(cat) {
244 return {id: cat.id(), value: egCore.idl.toHash(cat)}
247 return service.request({
250 action: 'insertOrReplace',
255 service.getStatCatsCache = function () {
257 return service.request({
261 }).then(function(list) {
263 list.forEach(function(s) {
264 var sc = egCore.idl.fromHash('actsc', s.value);
266 if (Array.isArray(sc.default_entries())) {
268 sc.default_entries().map( function (k) {
269 return egCore.idl.fromHash('actsced', k);
274 if (Array.isArray(sc.entries())) {
276 sc.entries().map( function (k) {
277 return egCore.idl.fromHash('actsce', k);
289 service.setSettingsCache = function (settings) {
290 if (lf.isOffline) return $q.when();
293 angular.forEach(settings, function (val, key) {
294 rows.push({name : key, value : JSON.stringify(val)});
297 return service.request({
300 action: 'insertOrReplace',
305 service.getSettingsCache = function (settings) {
309 if (settings && settings.length) {
310 promise = service.request({
313 action: 'selectWhereIn',
318 promise = service.request({
327 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
333 service.setListInOfflineCache = function (type, list) {
334 if (lf.isOffline) return $q.when();
336 return service.isCacheGood(type).then(function(good) {
337 if (good) { return }; // already cached
339 var pkey = egCore.idl.classes[type].pkey;
340 var rows = Object.values(list).map(function(item) {
343 id: '' + item[pkey](),
344 object: egCore.idl.toHash(item)
348 return service.request({
351 action: 'insertOrReplace',
353 }).then(function(resp) {
354 return service.request({
357 action: 'insertOrReplace',
358 rows: [{type: type, cachedate : new Date()}]
364 service.getListFromOfflineCache = function(type) {
365 return service.request({
368 action: 'selectWhereEqual',
371 }).then(function(resp) {
372 return resp.map(function(item) {
373 return egCore.idl.fromHash(type,item['object']);
378 service.reconstituteList = function(type) {
380 console.debug('egLovefield reading ' + type + ' list');
381 return service.getListFromOfflineCache(type).then(function (list) {
382 egCore.env.absorbList(list, type, true)
383 return $q.when(true);
386 return $q.when(false);
389 service.reconstituteTree = function(type) {
391 console.debug('egLovefield reading ' + type + ' tree');
393 var pkey = egCore.idl.classes[type].pkey;
394 var parent_field = 'parent';
397 parent_field = 'parent_ou';
400 return service.getListFromOfflineCache(type).then(function (list) {
403 angular.forEach(list, function (item) {
405 // Special case for aou, to reconstitue ou_type
407 if (item.ou_type()) {
408 item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
412 hash[''+item[pkey]()] = item;
413 if (!item[parent_field]()) {
415 } else if (angular.isObject(item[parent_field]())) {
416 // un-objectify the parent
418 item[parent_field]()[pkey]()
423 angular.forEach(list, function (item) {
424 item.children([]); // just clear it out if there's junk in there
426 item.children( list.filter(function (kid) {
427 return kid[parent_field]() == item[pkey]();
431 angular.forEach(list, function (item) {
432 if (item[parent_field]()) {
433 item[parent_field]( hash[''+item[parent_field]()] );
437 egCore.env.absorbTree(top, type, true)
441 return $q.when(false);