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.warn('SharedWorker() not supported', E);
31 service.cannotConnect = true;
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);
40 service.cannotConnect = true;
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];
51 console.error('Recieved response for unknown request ' + reqId);
55 if (response.status === 'OK') {
56 req.deferred.resolve(response.result);
58 console.error('worker request failed with ' + response.error);
59 req.deferred.reject(response.error);
63 service.worker.port.start();
66 service.connectToSchemas = function() {
68 service.connectToWorker(); // no-op if already connected
70 if (service.cannotConnect) {
71 // This can happen in certain environments
77 service.activeSchemas.forEach(function(schema) {
78 promises.push(service.connectToSchema(schema));
81 return $q.all(promises).then(
83 function() {service.cannotConnect = true}
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(
93 return service.relayRequest(args);
98 // Send a request to the web worker and register the request for
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++;
106 service.pendingRequests.push({id : reqId, deferred: deferred});
107 service.worker.port.postMessage(args);
108 return deferred.promise;
111 // Create and connect to the give schema
112 service.connectToSchema = function(schema) {
114 if (service.connectedSchemas.indexOf(schema) >= 0) {
119 if (service.schemasInProgress[schema]) {
120 return service.schemasInProgress[schema];
123 var deferred = $q.defer();
125 service.relayRequest(
126 {schema: schema, action: 'createSchema'})
129 return service.relayRequest(
130 {schema: schema, action: 'connect'});
135 service.connectedSchemas.push(schema);
136 delete service.schemasInProgress[schema];
142 return service.schemasInProgress[schema] = deferred.promise;
145 service.isCacheGood = function (type) {
146 return service.request({
149 action: 'selectWhereEqual',
155 if (!row) { return false; }
156 // hard-coded 1 day offline cache timeout
157 return (new Date().getTime() - row.cachedate.getTime()) <= 86400000;
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({
167 table: 'OfflineXact',
170 return service.request({
173 action: 'deleteWhereEqual',
175 value: '_offlineXact'
180 // Returns the cache date when xacts exit, null otherwise.
181 service.havePendingOfflineXacts = function () {
182 return service.request({
185 action: 'selectWhereEqual',
187 value: '_offlineXact'
188 }).then(function(results) {
189 return results[0] ? results[0].cachedate : null;
193 service.retrievePendingOfflineXacts = function () {
194 return service.request({
196 table: 'OfflineXact',
198 }).then(function(resp) {
199 return resp.map(function(x) { return x.value });
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({
208 table: 'OfflineXact',
209 action: 'insertOrReplace',
212 return service.request({
215 action: 'insertOrReplace',
216 rows: [{type: '_offlineXact', cachedate : new Date()}]
221 service.populateBlockList = function() {
222 return service.request({
223 action: 'populateBlockList',
224 authtoken: egCore.auth.token()
228 // Returns a promise with true for blocked, false for not blocked
229 service.testOfflineBlock = function (barcode) {
230 return service.request({
232 table: 'OfflineBlocks',
233 action: 'selectWhereEqual',
236 }).then(function(resp) {
237 if (resp.length === 0) return null;
238 return resp[0].reason;
242 service.setStatCatsCache = function (statcats) {
243 if (lf.isOffline || !statcats || statcats.length === 0)
246 var rows = statcats.map(function(cat) {
247 return {id: cat.id(), value: egCore.idl.toHash(cat)}
250 return service.request({
253 action: 'insertOrReplace',
258 service.getStatCatsCache = function () {
260 return service.request({
264 }).then(function(list) {
266 list.forEach(function(s) {
267 var sc = egCore.idl.fromHash('actsc', s.value);
269 if (Array.isArray(sc.default_entries())) {
271 sc.default_entries().map( function (k) {
272 return egCore.idl.fromHash('actsced', k);
277 if (Array.isArray(sc.entries())) {
279 sc.entries().map( function (k) {
280 return egCore.idl.fromHash('actsce', k);
292 service.setSettingsCache = function (settings) {
293 if (lf.isOffline) return $q.when();
296 angular.forEach(settings, function (val, key) {
297 rows.push({name : key, value : JSON.stringify(val)});
300 return service.request({
303 action: 'insertOrReplace',
308 service.getSettingsCache = function (settings) {
312 if (settings && settings.length) {
313 promise = service.request({
316 action: 'selectWhereIn',
321 promise = service.request({
330 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
336 service.setListInOfflineCache = function (type, list) {
337 if (lf.isOffline) return $q.when();
339 return service.isCacheGood(type).then(function(good) {
340 if (good) { return }; // already cached
342 var pkey = egCore.idl.classes[type].pkey;
343 var rows = Object.values(list).map(function(item) {
346 id: '' + item[pkey](),
347 object: egCore.idl.toHash(item)
351 return service.request({
354 action: 'insertOrReplace',
356 }).then(function(resp) {
357 return service.request({
360 action: 'insertOrReplace',
361 rows: [{type: type, cachedate : new Date()}]
367 service.getListFromOfflineCache = function(type) {
368 return service.request({
371 action: 'selectWhereEqual',
374 }).then(function(resp) {
375 return resp.map(function(item) {
376 return egCore.idl.fromHash(type,item['object']);
381 service.reconstituteList = function(type) {
383 console.debug('egLovefield reading ' + type + ' list');
384 return service.getListFromOfflineCache(type).then(function (list) {
385 egCore.env.absorbList(list, type, true)
386 return $q.when(true);
389 return $q.when(false);
392 service.reconstituteTree = function(type) {
394 console.debug('egLovefield reading ' + type + ' tree');
396 var pkey = egCore.idl.classes[type].pkey;
397 var parent_field = 'parent';
400 parent_field = 'parent_ou';
403 return service.getListFromOfflineCache(type).then(function (list) {
406 angular.forEach(list, function (item) {
408 // Special case for aou, to reconstitue ou_type
410 if (item.ou_type()) {
411 item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
415 hash[''+item[pkey]()] = item;
416 if (!item[parent_field]()) {
418 } else if (angular.isObject(item[parent_field]())) {
419 // un-objectify the parent
421 item[parent_field]()[pkey]()
426 angular.forEach(list, function (item) {
427 item.children([]); // just clear it out if there's junk in there
429 item.children( list.filter(function (kid) {
430 return kid[parent_field]() == item[pkey]();
434 angular.forEach(list, function (item) {
435 if (item[parent_field]()) {
436 item[parent_field]( hash[''+item[parent_field]()] );
440 egCore.env.absorbTree(top, type, true)
444 return $q.when(false);