]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/core/server-store.service.ts
LP1938729 Cache "cascade" setting values
[Evergreen.git] / Open-ILS / src / eg2 / src / app / core / server-store.service.ts
1 /**
2  * Set and get server-stored settings.
3  */
4 import {Injectable} from '@angular/core';
5 import {tap} from 'rxjs/operators';
6 import {AuthService} from './auth.service';
7 import {NetService} from './net.service';
8 import {DbStoreService} from './db-store.service';
9
10 // Settings summary objects returned by the API
11 interface ServerSettingSummary {
12     name: string;
13     value: string;
14     has_org_setting: boolean;
15     has_user_setting: boolean;
16     has_workstation_setting: boolean;
17 }
18
19 @Injectable({providedIn: 'root'})
20 export class ServerStoreService {
21
22     cache: {[key: string]: any};
23
24     constructor(
25         private db: DbStoreService,
26         private net: NetService,
27         private auth: AuthService) {
28         this.cache = {};
29     }
30
31     setItem(key: string, value: any): Promise<any> {
32
33         if (!this.auth.token()) {
34             return Promise.reject('Auth required to apply settings');
35         }
36
37         const setting: any = {};
38         setting[key] = value;
39
40         return this.net.request(
41             'open-ils.actor',
42             'open-ils.actor.settings.apply.user_or_ws',
43             this.auth.token(), setting)
44
45         .toPromise().then(appliedCount => {
46
47             if (Number(appliedCount) <= 0) { // no value applied
48                 return Promise.reject(
49                     `No user or workstation setting type exists for: "${key}".\n` +
50                     'Create a ws/user setting type or use setLocalItem() to ' +
51                     'store the value locally.'
52                 );
53             }
54
55             return this.addSettingsToDb(setting);
56         });
57     }
58
59     // Returns a single setting value
60     getItem(key: string): Promise<any> {
61         return this.getItemBatch([key]).then(
62             settings => settings[key]
63         );
64     }
65
66     // Sync call for items known to be cached locally.
67     getItemCached(key: string): any {
68         return this.cache[key];
69     }
70
71     // Sync batch call for items known to be cached locally
72     getItemBatchCached(keys: string[]): {[key: string]: any} {
73         const values: any = {};
74         keys.forEach(key => {
75             if (key in this.cache) {
76                 values[key] = this.cache[key];
77             }
78         });
79         return values;
80     }
81
82     // Returns a set of key/value pairs for the requested settings
83     getItemBatch(keys: string[]): Promise<{[key: string]: any}> {
84
85         let values: any = {};
86         keys.forEach(key => {
87             if (key in this.cache) {
88                 values[key] = this.cache[key];
89             }
90         });
91
92         if (keys.length === Object.keys(values).length) {
93             // All values are cached already
94             return Promise.resolve(values);
95         }
96
97         if (!this.auth.token()) {
98             // Authtokens require for fetching server settings, but
99             // calls to retrieve settings could potentially occur
100             // before auth completes -- Ideally not, but just to be safe.
101             return Promise.resolve({});
102         }
103
104         // IndexedDB call required.
105         const dbKeys = [];
106
107         keys.forEach(key => {
108             if (!Object.keys(values).includes(key)) {
109                 dbKeys.push(key);
110             }
111         });
112
113         return this.getSettingsFromDb(dbKeys) // Also appends to local cache.
114         .then(dbValues => values = Object.assign(values, dbValues))
115         .then(_ => {
116
117             const serverKeys = [];
118             keys.forEach(key => {
119                 if (!Object.keys(values).includes(key)) {
120                     serverKeys.push(key);
121                 }
122             });
123
124             if (serverKeys.length === 0) { return values; }
125
126             return this.net.request(
127                 'open-ils.actor',
128                 'open-ils.actor.settings.retrieve',
129                 serverKeys, this.auth.token()
130
131             ).pipe(tap((summary: ServerSettingSummary) => {
132                 this.cache[summary.name] =
133                     values[summary.name] = summary.value;
134
135             })).toPromise().then(__ => {
136
137                 const dbSets: any = {};
138                 serverKeys.forEach(sKey => dbSets[sKey] = values[sKey]);
139
140                 return this.addSettingsToDb(dbSets);
141
142             });
143         });
144     }
145
146     removeItem(key: string): Promise<any> {
147         return this.setItem(key, null);
148     }
149
150     private addSettingsToDb(values: {[key: string]: any}): Promise<{[key: string]: any}> {
151
152         const rows = [];
153         Object.keys(values).forEach(name => {
154             // Anything added to the db should also be cached locally.
155             this.cache[name] = values[name];
156             rows.push({name: name, value: JSON.stringify(values[name])});
157         });
158
159         if (rows.length === 0) { return Promise.resolve(values); }
160
161         return this.db.request({
162             schema: 'cache',
163             table: 'Setting',
164             action: 'insertOrReplace',
165             rows: rows
166         }).then(_ => values).catch(_ => values);
167     }
168
169     getSettingsFromDb(names: string[]): Promise<{[key: string]: any}> {
170         if (names.length === 0) { return Promise.resolve({}); }
171
172         const values: any = {};
173
174         return this.db.request({
175             schema: 'cache',
176             table: 'Setting',
177             action: 'selectWhereIn',
178             field: 'name',
179             value: names
180         }).then(settings => {
181
182             // array of key => JSON-string objects
183             settings.forEach(setting => {
184                 const value = JSON.parse(setting.value);
185                 // propagate to local cache as well
186                 values[setting.name] = this.cache[setting.name] = value;
187             });
188
189             return values;
190         }).catch(_ => values);
191     }
192 }
193