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...
31 service.worker = new SharedWorker(service.workerUrl);
33 console.warn('SharedWorker() not supported', E);
34 service.cannotConnect = true;
38 service.worker.onerror = function(err) {
39 // avoid spamming unit test runner on failure to connect.
40 if (!navigator.userAgent.match(/PhantomJS/)) {
41 console.error('Error loading shared worker', err);
43 service.cannotConnect = true;
46 // List for responses and resolve the matching pending request.
47 service.worker.port.addEventListener('message', function(evt) {
48 var response = evt.data;
49 var reqId = response.id;
50 var req = service.pendingRequests.filter(
51 function(r) { return r.id === reqId})[0];
54 console.error('Recieved response for unknown request ' + reqId);
58 if (response.status === 'OK') {
59 req.deferred.resolve(response.result);
61 console.error('worker request failed with ' + response.error);
62 req.deferred.reject(response.error);
66 service.worker.port.start();
70 service.connectToSchemas = function() {
72 service.connectToWorker(); // no-op if already connected
74 if (service.cannotConnect) {
75 // This can happen in certain environments
81 service.activeSchemas.forEach(function(schema) {
82 promises.push(service.connectToSchema(schema));
85 return $q.all(promises).then(
87 function() {service.cannotConnect = true}
91 // Connects if necessary to the active schemas then relays the request.
92 service.request = function(args) {
93 // useful, but very chatty, leaving commented out.
94 // console.debug('egLovfield sending request: ', args);
95 return service.connectToSchemas().then(
97 return service.relayRequest(args);
102 // Send a request to the web worker and register the request for
103 // future resolution.
104 // Store the request ID in the request arguments, so it's included
105 // in the response, and in the pendingRequests list for linking.
106 service.relayRequest = function(args) {
107 var deferred = $q.defer();
108 var reqId = service.autoId++;
110 service.pendingRequests.push({id : reqId, deferred: deferred});
111 service.worker.port.postMessage(args);
112 return deferred.promise;
115 // Create and connect to the give schema
116 service.connectToSchema = function(schema) {
118 if (service.connectedSchemas.indexOf(schema) >= 0) {
123 if (service.schemasInProgress[schema]) {
124 return service.schemasInProgress[schema];
127 var deferred = $q.defer();
129 service.relayRequest(
130 {schema: schema, action: 'createSchema'})
133 return service.relayRequest(
134 {schema: schema, action: 'connect'});
139 service.connectedSchemas.push(schema);
140 delete service.schemasInProgress[schema];
146 return service.schemasInProgress[schema] = deferred.promise;
149 service.isCacheGood = function (type) {
150 if (lf.isOffline || !service.connectToWorker()) return $q.when(true);
152 return service.request({
155 action: 'selectWhereEqual',
161 if (!row) { return false; }
162 // hard-coded 1 day offline cache timeout
163 return (new Date().getTime() - row.cachedate.getTime()) <= 86400000;
168 // Remove all pending offline transactions and delete the cached
169 // offline transactions date to indicate no transactions remain.
170 service.destroyPendingOfflineXacts = function () {
171 return service.request({
173 table: 'OfflineXact',
176 return service.request({
179 action: 'deleteWhereEqual',
181 value: '_offlineXact'
186 // Returns the cache date when xacts exit, null otherwise.
187 service.havePendingOfflineXacts = function () {
188 return service.request({
191 action: 'selectWhereEqual',
193 value: '_offlineXact'
194 }).then(function(results) {
195 return results[0] ? results[0].cachedate : null;
199 service.retrievePendingOfflineXacts = function () {
200 return service.request({
202 table: 'OfflineXact',
204 }).then(function(resp) {
205 return resp.map(function(x) { return x.value });
209 // Add an offline transaction and update the cache indicating
210 // now() as the most recent addition of an offline xact.
211 service.addOfflineXact = function (obj) {
212 return service.request({
214 table: 'OfflineXact',
215 action: 'insertOrReplace',
218 return service.request({
221 action: 'insertOrReplace',
222 rows: [{type: '_offlineXact', cachedate : new Date()}]
227 service.populateBlockList = function() {
228 return service.request({
229 action: 'populateBlockList',
230 authtoken: egCore.auth.token()
234 // Returns a promise with true for blocked, false for not blocked
235 service.testOfflineBlock = function (barcode) {
236 return service.request({
238 table: 'OfflineBlocks',
239 action: 'selectWhereEqual',
242 }).then(function(resp) {
243 if (resp.length === 0) return null;
244 return resp[0].reason;
248 service.setStatCatsCache = function (statcats) {
249 if (lf.isOffline || !statcats ||
250 statcats.length === 0 || !service.connectToWorker()) {
254 var rows = statcats.map(function(cat) {
255 return {id: cat.id(), value: egCore.idl.toHash(cat)}
258 return service.request({
261 action: 'insertOrReplace',
266 service.getStatCatsCache = function () {
267 return service.request({
271 }).then(function(list) {
273 list.forEach(function(s) {
274 var sc = egCore.idl.fromHash('actsc', s.value);
276 if (Array.isArray(sc.default_entries())) {
278 sc.default_entries().map( function (k) {
279 return egCore.idl.fromHash('actsced', k);
284 if (Array.isArray(sc.entries())) {
286 sc.entries().map( function (k) {
287 return egCore.idl.fromHash('actsce', k);
299 service.setSettingsCache = function (settings) {
300 if (lf.isOffline || !service.connectToWorker()) return $q.when();
303 angular.forEach(settings, function (val, key) {
304 rows.push({name : key, value : JSON.stringify(val)});
307 return service.request({
310 action: 'insertOrReplace',
315 service.getSettingsCache = function (settings) {
316 if (lf.isOffline || !service.connectToWorker()) return $q.when([]);
320 if (settings && settings.length) {
321 promise = service.request({
324 action: 'selectWhereIn',
329 promise = service.request({
338 resp.forEach(function(s) { s.value = JSON.parse(s.value); });
344 service.destroySettingsCache = function () {
345 if (lf.isOffline || !service.connectToWorker()) return $q.when();
346 return service.request({
353 service.setListInOfflineCache = function (type, list) {
354 if (lf.isOffline || !service.connectToWorker()) return $q.when();
356 return service.isCacheGood(type).then(function(good) {
357 if (good) { return }; // already cached
359 var pkey = egCore.idl.classes[type].pkey;
360 var rows = Object.values(list).map(function(item) {
363 id: '' + item[pkey](),
364 object: egCore.idl.toHash(item)
368 return service.request({
371 action: 'insertOrReplace',
373 }).then(function(resp) {
374 return service.request({
377 action: 'insertOrReplace',
378 rows: [{type: type, cachedate : new Date()}]
384 service.getListFromOfflineCache = function(type) {
385 return service.request({
388 action: 'selectWhereEqual',
391 }).then(function(resp) {
392 return resp.map(function(item) {
393 return egCore.idl.fromHash(type,item['object']);
398 service.reconstituteList = function(type) {
400 console.debug('egLovefield reading ' + type + ' list');
401 return service.getListFromOfflineCache(type).then(function (list) {
402 egCore.env.absorbList(list, type, true)
403 return $q.when(true);
406 return $q.when(false);
409 service.reconstituteTree = function(type) {
411 console.debug('egLovefield reading ' + type + ' tree');
413 var pkey = egCore.idl.classes[type].pkey;
414 var parent_field = 'parent';
417 parent_field = 'parent_ou';
420 return service.getListFromOfflineCache(type).then(function (list) {
423 angular.forEach(list, function (item) {
425 // Special case for aou, to reconstitue ou_type
427 if (item.ou_type()) {
428 item.ou_type( egCore.idl.fromHash('aout', item.ou_type()) );
432 hash[''+item[pkey]()] = item;
433 if (!item[parent_field]()) {
435 } else if (angular.isObject(item[parent_field]())) {
436 // un-objectify the parent
438 item[parent_field]()[pkey]()
443 angular.forEach(list, function (item) {
444 item.children([]); // just clear it out if there's junk in there
446 item.children( list.filter(function (kid) {
447 return kid[parent_field]() == item[pkey]();
451 angular.forEach(list, function (item) {
452 if (item[parent_field]()) {
453 item[parent_field]( hash[''+item[parent_field]()] );
458 // Sort the org tree before absorbing
459 egCore.env.sort_aou(top);
462 egCore.env.absorbTree(top, type, true)
466 return $q.when(false);