Cache API

This commit is contained in:
Jiang Yio 2023-04-29 18:23:20 -04:00
parent a3413e382c
commit a43ca627a9
5 changed files with 97 additions and 44 deletions

View File

@ -14,6 +14,15 @@ export async function close(cid) {
})).json(); })).json();
} }
export async function call(cid, body) {
return await (await fetch('/v1/vista/' + cid, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})).json();
}
/*
export async function call(cid, method, ...params) { export async function call(cid, method, ...params) {
return await (await fetch('/v1/vista/' + cid, { return await (await fetch('/v1/vista/' + cid, {
method: 'POST', method: 'POST',
@ -29,6 +38,7 @@ export async function callctx(cid, context, method, ...params) {
body: JSON.stringify({ method: method, params: params, context: context, id: Date.now() }) body: JSON.stringify({ method: method, params: params, context: context, id: Date.now() })
})).json(); })).json();
} }
*/
export async function serverinfo(cid) { export async function serverinfo(cid) {
return await (await fetch('/v1/vista/' + cid + '/serverinfo', { return await (await fetch('/v1/vista/' + cid + '/serverinfo', {
@ -55,5 +65,5 @@ export async function authenticate(cid, avcode=null) {
} }
export default window.vista = { export default window.vista = {
connect, close, call, callctx, serverinfo, userinfo, authenticate connect, close, call, serverinfo, userinfo, authenticate
}; };

View File

@ -33,7 +33,10 @@ export function unwrapped(fn) {
return async function(...args) { return async function(...args) {
var res = await fn(...args); var res = await fn(...args);
if(res.error) throw new RPCError(res.error.type, ...res.error.args); if(res.error) throw new RPCError(res.error.type, ...res.error.args);
else return res.result; if(res.ts) try {
res.result._ts = res.ts;
} catch(ex) {}
return res.result;
} }
} }
@ -289,15 +292,19 @@ export function Client(cid, secret) {
this.connected.value = false; this.connected.value = false;
return vista.close(cid); return vista.close(cid);
}; };
this.call = async function(method, ...params) { this.call = async function(body, ...params) {
var res = await vista.call(cid, method, ...params); body = (typeof body === 'string') || (body instanceof String) ? { method: body, params, id: Date.now() } : Object.assign({ id: Date.now() }, body);
if(params.length > 0) {
if(body.params) Array.prototype.push.apply(body.params, params);
else body.params = params;
}
var res = await vista.call(cid, body);
if((res.error) && (res.error.type == 'ConnectionResetError')) this.close(); if((res.error) && (res.error.type == 'ConnectionResetError')) this.close();
res._request = body;
return res; return res;
}; };
this.callctx = async function(context, method, ...params) { this.callctx = function(context, method, ...params) {
var res = vista.callctx(cid, context, method, ...params); return this.call({ context, method, params, id: Date.now() });
if((res.error) && (res.error.type == 'ConnectionResetError')) this.close();
return res;
}; };
this.heartbeat = async function(interval=null) { this.heartbeat = async function(interval=null) {
if(!interval) interval = 0.45*1000*(await this.XWB_GET_BROKER_INFO())[0]; if(!interval) interval = 0.45*1000*(await this.XWB_GET_BROKER_INFO())[0];
@ -332,15 +339,15 @@ export function Client(cid, secret) {
if(localstate.practitioner) delete localstate.practitioner; if(localstate.practitioner) delete localstate.practitioner;
}; };
this.XWB_IM_HERE = unwrapped(logged(() => this.call('XWB_IM_HERE'), 'XWB_IM_HERE')); this.XWB_IM_HERE = unwrapped(logged(() => this.call({ method: 'XWB_IM_HERE', ttl: 30 }), 'XWB_IM_HERE'));
this.XUS_INTRO_MSG = memoized(unwrapped(logged(() => this.callctx(['XUCOMMAND'], 'XUS_INTRO_MSG'), 'XUS_INTRO_MSG'))); this.XUS_INTRO_MSG = memoized(unwrapped(logged(() => this.callctx(['XUCOMMAND'], 'XUS_INTRO_MSG'), 'XUS_INTRO_MSG')));
this.XWB_GET_BROKER_INFO = memoized(unwrapped(logged(() => this.callctx(['XUCOMMAND'], 'XWB_GET_BROKER_INFO'), 'XWB_GET_BROKER_INFO'))); this.XWB_GET_BROKER_INFO = memoized(unwrapped(logged(() => this.callctx(['XUCOMMAND'], 'XWB_GET_BROKER_INFO'), 'XWB_GET_BROKER_INFO')));
this.XUS_GET_USER_INFO = memoized(unwrapped(logged(() => this.call('XUS_GET_USER_INFO'), 'XUS_GET_USER_INFO'))); this.XUS_GET_USER_INFO = memoized(unwrapped(logged(() => this.call('XUS_GET_USER_INFO'), 'XUS_GET_USER_INFO')));
this.SDEC_RESOURCE = memoized(unwrapped(logged(() => this.callctx(['SDECRPC'], 'SDEC_RESOURCE'), 'SDEC_RESOURCE'))); this.SDEC_RESOURCE = memoized(unwrapped(logged(() => this.call({ method: 'SDEC_RESOURCE', context: ['SDECRPC'], ttl: 2592000 }), 'SDEC_RESOURCE')));
this.SDEC_CLINLET = memoized(unwrapped(logged((...args) => this.callctx(['SDECRPC'], 'SDEC_CLINLET', ...args), 'SDEC_CLINLET'))); this.SDEC_CLINLET = unwrapped(logged((...args) => this.call({ method: 'SDEC_CLINLET', context: ['SDECRPC'], ttl: 30 }, ...args), 'SDEC_CLINLET'));
this.SDEC_CRSCHED = unwrapped(logged((...args) => this.callctx(['SDECRPC'], 'SDEC_CRSCHED', ...args), 'SDEC_CRSCHED')); this.SDEC_CRSCHED = unwrapped(logged((...args) => this.call({ method: 'SDEC_CRSCHED', context: ['SDECRPC'], ttl: 30 }, ...args), 'SDEC_CRSCHED'));
this.ORWPT_FULLSSN = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT_FULLSSN', ...args), 'ORWPT_FULLSSN')), ['dfn', 'name', 'date', 'pid'])); this.ORWPT_FULLSSN = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT_FULLSSN', ...args), 'ORWPT_FULLSSN')), ['dfn', 'name', 'date', 'pid']));
this.ORWPT_LAST5 = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT_LAST5', ...args), 'ORWPT_LAST5')), ['dfn', 'name', 'date', 'pid'])); this.ORWPT_LAST5 = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT_LAST5', ...args), 'ORWPT_LAST5')), ['dfn', 'name', 'date', 'pid']));
@ -362,15 +369,15 @@ export function Client(cid, secret) {
this.ORWORR_AGET = memoized(caretseparated(sliced(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWORR_AGET', ...args), 'ORWORR_AGET')), 1), ['ifn', 'dgrp', 'time'])); this.ORWORR_AGET = memoized(caretseparated(sliced(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWORR_AGET', ...args), 'ORWORR_AGET')), 1), ['ifn', 'dgrp', 'time']));
this.ORWORR_GET4LST = memoized(parsed_orderinfo(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWORR_GET4LST', ...args), 'ORWORR_GET4LST')))); this.ORWORR_GET4LST = memoized(parsed_orderinfo(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWORR_GET4LST', ...args), 'ORWORR_GET4LST'))));
this.TIU_TEMPLATE_GETROOTS = caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_GETROOTS', ...args), 'TIU_TEMPLATE_GETROOTS')), ['IEN', 'type', 'status', 'name', 'exclude_from_group_boilerplate', 'blank_lines', 'personal_owner', 'has_children_flag', 'dialog', 'display_only', 'first_line', 'one_item_only', 'hide_dialog_items', 'hide_tree_items', 'indent_items', 'reminder_dialog_ien', 'reminder_dialog_name', 'locked', 'com_object_pointer', 'com_object_parameter', 'link_pointer', 'reminder_dialog_patient_specific_value']); this.TIU_TEMPLATE_GETROOTS = caretseparated(unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_GETROOTS', context: ['OR CPRS GUI CHART'], ttl: 86400 }, ...args), 'TIU_TEMPLATE_GETROOTS')), ['IEN', 'type', 'status', 'name', 'exclude_from_group_boilerplate', 'blank_lines', 'personal_owner', 'has_children_flag', 'dialog', 'display_only', 'first_line', 'one_item_only', 'hide_dialog_items', 'hide_tree_items', 'indent_items', 'reminder_dialog_ien', 'reminder_dialog_name', 'locked', 'com_object_pointer', 'com_object_parameter', 'link_pointer', 'reminder_dialog_patient_specific_value']);
this.TIU_TEMPLATE_GETPROOT = caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_GETPROOT', ...args), 'TIU_TEMPLATE_GETPROOT')), ['IEN', 'type', 'status', 'name', 'exclude_from_group_boilerplate', 'blank_lines', 'personal_owner', 'has_children_flag', 'dialog', 'display_only', 'first_line', 'one_item_only', 'hide_dialog_items', 'hide_tree_items', 'indent_items', 'reminder_dialog_ien', 'reminder_dialog_name', 'locked', 'com_object_pointer', 'com_object_parameter', 'link_pointer', 'reminder_dialog_patient_specific_value']); this.TIU_TEMPLATE_GETPROOT = caretseparated(unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_GETPROOT', context: ['OR CPRS GUI CHART'], ttl: 86400 }, ...args), 'TIU_TEMPLATE_GETPROOT')), ['IEN', 'type', 'status', 'name', 'exclude_from_group_boilerplate', 'blank_lines', 'personal_owner', 'has_children_flag', 'dialog', 'display_only', 'first_line', 'one_item_only', 'hide_dialog_items', 'hide_tree_items', 'indent_items', 'reminder_dialog_ien', 'reminder_dialog_name', 'locked', 'com_object_pointer', 'com_object_parameter', 'link_pointer', 'reminder_dialog_patient_specific_value']);
this.TIU_TEMPLATE_GETBOIL = parsed_text(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_GETBOIL', ...args), 'TIU_TEMPLATE_GETBOIL'))); this.TIU_TEMPLATE_GETBOIL = parsed_text(unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_GETBOIL', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'TIU_TEMPLATE_GETBOIL')));
this.TIU_TEMPLATE_GETITEMS = caretseparated(parsed_nullarray(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_GETITEMS', ...args), 'TIU_TEMPLATE_GETITEMS'))), ['IEN', 'type', 'status', 'name', 'exclude_from_group_boilerplate', 'blank_lines', 'personal_owner', 'has_children_flag', 'dialog', 'display_only', 'first_line', 'one_item_only', 'hide_dialog_items', 'hide_tree_items', 'indent_items', 'reminder_dialog_ien', 'reminder_dialog_name', 'locked', 'com_object_pointer', 'com_object_parameter', 'link_pointer', 'reminder_dialog_patient_specific_value']); this.TIU_TEMPLATE_GETITEMS = caretseparated(parsed_nullarray(unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_GETITEMS', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'TIU_TEMPLATE_GETITEMS'))), ['IEN', 'type', 'status', 'name', 'exclude_from_group_boilerplate', 'blank_lines', 'personal_owner', 'has_children_flag', 'dialog', 'display_only', 'first_line', 'one_item_only', 'hide_dialog_items', 'hide_tree_items', 'indent_items', 'reminder_dialog_ien', 'reminder_dialog_name', 'locked', 'com_object_pointer', 'com_object_parameter', 'link_pointer', 'reminder_dialog_patient_specific_value']);
this.TIU_TEMPLATE_SET_ITEMS = unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_SET_ITEMS', ...args), 'TIU_TEMPLATE_SET_ITEMS')); this.TIU_TEMPLATE_SET_ITEMS = unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_SET_ITEMS', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'TIU_TEMPLATE_SET_ITEMS'));
this.TIU_TEMPLATE_CREATE_MODIFY = unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_CREATE/MODIFY', ...args), 'TIU_TEMPLATE_CREATE/MODIFY')); this.TIU_TEMPLATE_CREATE_MODIFY = unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_CREATE/MODIFY', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'TIU_TEMPLATE_CREATE/MODIFY'));
this.TIU_TEMPLATE_DELETE = unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_DELETE', ...args), 'TIU_TEMPLATE_DELETE')); this.TIU_TEMPLATE_DELETE = unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_DELETE', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'TIU_TEMPLATE_DELETE'));
this.TIU_TEMPLATE_LOCK = unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_LOCK', ...args), 'TIU_TEMPLATE_LOCK')); this.TIU_TEMPLATE_LOCK = unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_LOCK', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'TIU_TEMPLATE_LOCK'));
this.TIU_TEMPLATE_UNLOCK = unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'TIU_TEMPLATE_UNLOCK', ...args), 'TIU_TEMPLATE_UNLOCK')); this.TIU_TEMPLATE_UNLOCK = unwrapped(logged((...args) => this.call({ method: 'TIU_TEMPLATE_UNLOCK', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'TIU_TEMPLATE_UNLOCK'));
this.ORWCV_VST = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWCV_VST', ...args), 'ORWCV_VST')), ['apptinfo', 'datetime', 'location', 'status'])); this.ORWCV_VST = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWCV_VST', ...args), 'ORWCV_VST')), ['apptinfo', 'datetime', 'location', 'status']));
@ -383,11 +390,11 @@ export function Client(cid, secret) {
this.ORWDX_DLGID = memoized(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDX_DLGID', ...args), 'ORWDX_DLGID'))); this.ORWDX_DLGID = memoized(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDX_DLGID', ...args), 'ORWDX_DLGID')));
this.ORWDX_DLGDEF = memoized(mapped(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDX_DLGDEF', ...args), 'ORWDX_DLGDEF')), ['promptID', 'promptIEN', 'fmtSeq', 'fmtCode', 'omit', 'lead', 'trail', 'newLine', 'wrap', 'children', 'isChild']), 'promptID')); this.ORWDX_DLGDEF = memoized(mapped(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDX_DLGDEF', ...args), 'ORWDX_DLGDEF')), ['promptID', 'promptIEN', 'fmtSeq', 'fmtCode', 'omit', 'lead', 'trail', 'newLine', 'wrap', 'children', 'isChild']), 'promptID'));
this.ORWDX_LOADRSP = memoized(mapped(parsed_orderoverrides(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDX_LOADRSP', ...args), 'ORWDX_LOADRSP')), ['promptID', 'promptIEN', 'fmtSeq', 'fmtCode', 'omit', 'lead', 'trail', 'newLine', 'wrap', 'children', 'isChild']), 'promptID')); this.ORWDX_LOADRSP = memoized(mapped(parsed_orderoverrides(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDX_LOADRSP', ...args), 'ORWDX_LOADRSP')), ['promptID', 'promptIEN', 'fmtSeq', 'fmtCode', 'omit', 'lead', 'trail', 'newLine', 'wrap', 'children', 'isChild']), 'promptID'));
this.ORWDX_SAVE = unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDX_SAVE', ...args), 'ORWDX_SAVE')); this.ORWDX_SAVE = unwrapped(logged((...args) => this.call({ method: 'ORWDX_SAVE', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'ORWDX_SAVE'));
this.ORWDXM_MENU = memoized(parsed_ordermenu(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDXM_MENU', ...args), 'ORWDXM_MENU')))); this.ORWDXM_MENU = memoized(parsed_ordermenu(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDXM_MENU', ...args), 'ORWDXM_MENU'))));
this.ORWDXM_DLGNAME = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDXM_DLGNAME', ...args), 'ORWDXM_DLGNAME')), ['InternalName', 'DisplayName', 'BaseDialogIEN', 'BaseDialogName'])); this.ORWDXM_DLGNAME = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDXM_DLGNAME', ...args), 'ORWDXM_DLGNAME')), ['InternalName', 'DisplayName', 'BaseDialogIEN', 'BaseDialogName']));
this.ORWDXM_PROMPTS = memoized(mapped(parsed_caretseparated_detail(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDXM_PROMPTS', ...args), 'ORWDXM_PROMPTS')), ['id', 'req', 'hid', 'prompt', 'type', 'domain', 'default', 'idflt', 'help']), 'id')); this.ORWDXM_PROMPTS = memoized(mapped(parsed_caretseparated_detail(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDXM_PROMPTS', ...args), 'ORWDXM_PROMPTS')), ['id', 'req', 'hid', 'prompt', 'type', 'domain', 'default', 'idflt', 'help']), 'id'));
this.ORWDXM1_BLDQRSP = caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWDXM1_BLDQRSP', ...args), 'ORWDXM1_BLDQRSP')), ['QuickLevel', 'ResponseID', 'Dialog', 'Type', 'FormID', 'DGrpLST']); this.ORWDXM1_BLDQRSP = caretseparated(unwrapped(logged((...args) => this.call({ method: 'ORWDXM1_BLDQRSP', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), 'ORWDXM1_BLDQRSP')), ['QuickLevel', 'ResponseID', 'Dialog', 'Type', 'FormID', 'DGrpLST']);
this.ORWUL_FV4DG = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWUL_FV4DG', ...args), 'ORWUL_FV4DG')), ['IEN', 'count'])); this.ORWUL_FV4DG = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWUL_FV4DG', ...args), 'ORWUL_FV4DG')), ['IEN', 'count']));
this.ORWUL_FVSUB = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWUL_FVSUB', ...args), 'ORWUL_FVSUB')), ['IEN', 'description'])); this.ORWUL_FVSUB = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWUL_FVSUB', ...args), 'ORWUL_FVSUB')), ['IEN', 'description']));
this.ORWUL_FVIDX = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWUL_FVIDX', ...args), 'ORWUL_FVIDX')), ['index', 'description'])); this.ORWUL_FVIDX = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWUL_FVIDX', ...args), 'ORWUL_FVIDX')), ['index', 'description']));
@ -456,7 +463,7 @@ Client.fromCookie = async function(secret, defaulthost='vista.northport.med.va.g
console.log('Using saved secret and connection', secret); console.log('Using saved secret and connection', secret);
var cid = localstate.cid; var cid = localstate.cid;
var client = Client.fromID(cid, secret); var client = Client.fromID(cid, secret);
if((await vista.call(cid, 'XWB_IM_HERE')).result == '1') { if((await vista.call(cid, { method: 'XWB_IM_HERE', ttl: 30, id: Date.now() })).result == '1') {
var server = await client.serverinfo(); var server = await client.serverinfo();
if((host[0] == server.result.host) && (host[1] == server.result.port)) { if((host[0] == server.result.host) && (host[1] == server.result.port)) {
localstate.host = host.join(':'); localstate.host = host.join(':');

57
main.py
View File

@ -29,7 +29,7 @@ class CacheProxyRPC(util.CacheProxy):
util.CacheProxy.__init__(self, obj) util.CacheProxy.__init__(self, obj)
if volatile is None: if volatile is None:
volatile = util.Store().memo volatile = util.Store().memo
self._cache(('__call__', 'close', 'authenticate', 'keepalive', 'XWB_CREATE_CONTEXT', 'XWB_IM_HERE', 'TIU_TEMPLATE_GETROOTS', 'TIU_TEMPLATE_GETPROOT', 'TIU_TEMPLATE_GETBOIL', 'TIU_TEMPLATE_GET_DESCRIPTION', 'TIU_TEMPLATE_GETITEMS', 'TIU_TEMPLATE_SET ITEMS', 'TIU_TEMPLATE_CREATE/MODIFY', 'TIU_TEMPLATE_DELETE', 'TIU_TEMPLATE_LOCK', 'TIU_TEMPLATE_UNLOCK', 'SDEC_CRSCHED', 'ORWDX_SAVE', 'ORWDXM1_BLDQRSP'), None) self._cache(('__call__', 'close', 'authenticate', 'keepalive', 'XWB_CREATE_CONTEXT', 'TIU_TEMPLATE_SET ITEMS', 'TIU_TEMPLATE_CREATE/MODIFY', 'TIU_TEMPLATE_DELETE', 'TIU_TEMPLATE_LOCK', 'TIU_TEMPLATE_UNLOCK', 'ORWDX_SAVE', 'ORWDXM1_BLDQRSP'), None)
self._cache(('XWB_GET_BROKER_INFO', 'XUS_INTRO_MSG'), volatile, prefix=prefix, ttl=float('inf')) self._cache(('XWB_GET_BROKER_INFO', 'XUS_INTRO_MSG'), volatile, prefix=prefix, ttl=float('inf'))
self._cache(None, volatile, prefix=prefix, ttl=float('-inf')) self._cache(None, volatile, prefix=prefix, ttl=float('-inf'))
self._cache_persistent(persistent=persistent, prefix=prefix) self._cache_persistent(persistent=persistent, prefix=prefix)
@ -38,6 +38,12 @@ class CacheProxyRPC(util.CacheProxy):
persistent = util.Store().memo persistent = util.Store().memo
self._cache(('SDEC_RESOURCE', 'ORWU1_NEWLOC', 'ORWLRR_ALLTESTS_ALL', 'ORWORDG_ALLTREE', 'ORWORDG_REVSTS', 'ORWDX_DGNM', 'ORWDX_ORDITM'), persistent, prefix=prefix, ttl=float('inf')) self._cache(('SDEC_RESOURCE', 'ORWU1_NEWLOC', 'ORWLRR_ALLTESTS_ALL', 'ORWORDG_ALLTREE', 'ORWORDG_REVSTS', 'ORWDX_DGNM', 'ORWDX_ORDITM'), persistent, prefix=prefix, ttl=float('inf'))
def jsonify_result(value, id=None):
return jsonify({ 'result': value._base, 'error': None, 'id': request.json.get('id'), 'ts': value._ts } if isinstance(value, util.Cached) else { 'result': value, 'error': None, 'id': request.json.get('id') })
def jsonify_error(ex, id=None):
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': id })
def application(): def application():
app = Flask(__name__) app = Flask(__name__)
app.json = JSONProviderX(app) app.json = JSONProviderX(app)
@ -58,12 +64,12 @@ def application():
while cid in clients: while cid in clients:
cid = ''.join(secrets.choice(string.ascii_lowercase + string.digits) for i in range(64)) cid = ''.join(secrets.choice(string.ascii_lowercase + string.digits) for i in range(64))
clients[cid] = client = CacheProxyRPC(rpc.ClientSync(host=params.get('host', 'test.northport.med.va.gov'), port=int(params.get('port', 19009)))) clients[cid] = client = CacheProxyRPC(rpc.ClientSync(host=params.get('host', 'test.northport.med.va.gov'), port=int(params.get('port', 19009))))
return jsonify({ 'result': cid, 'error': None, 'id': request.json.get('id') }) return jsonify_result(cid, id=request.json.get('id'))
else: else:
return jsonify({ 'result': None, 'error': { 'type': 'Unauthorized', 'args': [] }, 'id': request.json.get('id') }) return jsonify({ 'result': None, 'error': { 'type': 'Unauthorized', 'args': [] }, 'id': request.json.get('id') })
except Exception as ex: except Exception as ex:
logger.exception(request.url) logger.exception(request.url)
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': request.json.get('id') }) return jsonify_error(ex, id=request.json.get('id'))
@app.post('/v1/vista/<cid>/close') @app.post('/v1/vista/<cid>/close')
def cb_close(cid): def cb_close(cid):
@ -71,28 +77,28 @@ def application():
client = clients[cid] client = clients[cid]
res = client.close() res = client.close()
del clients[cid] del clients[cid]
return jsonify({ 'result': res, 'error': None, 'id': request.json.get('id') }) return jsonify_result(res, id=request.json.get('id'))
except Exception as ex: except Exception as ex:
logger.exception(request.url) logger.exception(request.url)
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': request.json.get('id') }) return jsonify_error(ex, id=request.json.get('id'))
@app.post('/v1/vista/<cid>/serverinfo') @app.post('/v1/vista/<cid>/serverinfo')
def cb_serverinfo(cid): def cb_serverinfo(cid):
try: try:
client = clients[cid] client = clients[cid]
return jsonify({ 'result': client._obj._server, 'error': None, 'id': request.json.get('id') }) return jsonify_result(client._obj._server, id=request.json.get('id'))
except Exception as ex: except Exception as ex:
logger.exception(request.url) logger.exception(request.url)
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': request.json.get('id') }) return jsonify_error(ex, id=request.json.get('id'))
@app.post('/v1/vista/<cid>/userinfo') @app.post('/v1/vista/<cid>/userinfo')
def cb_userinfo(cid): def cb_userinfo(cid):
try: try:
client = clients[cid] client = clients[cid]
return jsonify({ 'result': client._obj._user, 'error': None, 'id': request.json.get('id') }) return jsonify_result(client._obj._user, id=request.json.get('id'))
except Exception as ex: except Exception as ex:
logger.exception(request.url) logger.exception(request.url)
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': request.json.get('id') }) return jsonify_error(ex, id=request.json.get('id'))
@app.post('/v1/vista/<cid>/authenticate') @app.post('/v1/vista/<cid>/authenticate')
def cb_authenticate(cid): def cb_authenticate(cid):
@ -102,41 +108,56 @@ def application():
if 'avcode' in params: if 'avcode' in params:
user = client.authenticate(params['avcode']) user = client.authenticate(params['avcode'])
client._cache_persistent(persistent=util.Store(f'cache.{client._server["volume"].lower()}.{client._server["uci"].lower()}.{user[0]}.db', journal_mode='WAL').memo) client._cache_persistent(persistent=util.Store(f'cache.{client._server["volume"].lower()}.{client._server["uci"].lower()}.{user[0]}.db', journal_mode='WAL').memo)
return jsonify({ 'result': user, 'error': None, 'id': request.json.get('id') }) return jsonify_result(user, id=request.json.get('id'))
else: else:
from auth import XUIAMSSOi_MySsoTokenVBA from auth import XUIAMSSOi_MySsoTokenVBA
if token := XUIAMSSOi_MySsoTokenVBA(): if token := XUIAMSSOi_MySsoTokenVBA():
user = client.authenticate(token) user = client.authenticate(token)
client._cache_persistent(persistent=util.Store(f'cache.{client._server["volume"].lower()}.{client._server["uci"].lower()}.{user[0]}.db', journal_mode='WAL').memo) client._cache_persistent(persistent=util.Store(f'cache.{client._server["volume"].lower()}.{client._server["uci"].lower()}.{user[0]}.db', journal_mode='WAL').memo)
return jsonify({ 'result': user, 'error': None, 'id': request.json.get('id') }) return jsonify_result(user, id=request.json.get('id'))
else: else:
return jsonify({ 'result': None, 'error': { 'type': 'Unauthorized', 'args': [] }, 'id': request.json.get('id') }) return jsonify({ 'result': None, 'error': { 'type': 'Unauthorized', 'args': [] }, 'id': request.json.get('id') })
except Exception as ex: except Exception as ex:
logger.exception(request.url) logger.exception(request.url)
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': request.json.get('id') }) return jsonify_error(ex, id=request.json.get('id'))
@app.post('/v1/vista/<cid>') @app.post('/v1/vista/<cid>')
def cb_call1(cid): def cb_call1(cid):
try: try:
client = clients[cid] client = clients[cid]
data = request.json data = request.json
kw = {}
if 'context' in data: if 'context' in data:
return jsonify({ 'result': getattr(client, data['method'].upper())(*data.get('params', ()), context=data['context']), 'error': None, 'id': data.get('id') }) kw['context'] = data['context']
else: thunk = getattr(client, data['method'].upper())
return jsonify({ 'result': getattr(client, data['method'].upper())(*data.get('params', ())), 'error': None, 'id': data.get('id') }) if getattr(thunk, 'cached', False):
if 'ttl' in data:
kw['_cache_ttl'] = data['ttl']
if 'stale' in data:
kw['_cache_stale'] = data['stale']
return jsonify_result(thunk(*data.get('params', ()), **kw), id=data.get('id'))
except Exception as ex: except Exception as ex:
logger.exception(request.url) logger.exception(request.url)
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': request.json.get('id') }) return jsonify_error(ex, id=request.json.get('id'))
@app.post('/v1/vista/<cid>/<method>') @app.post('/v1/vista/<cid>/<method>')
def cb_call2(cid, method): def cb_call2(cid, method):
try: try:
client = clients[cid] client = clients[cid]
data = request.json data = request.json
return jsonify({ 'result': getattr(client, method.upper())(*data.get('params', ())), 'error': None, 'id': data.get('id') }) kw = {}
if 'context' in data:
kw['context'] = data['context']
thunk = getattr(client, method.upper())
if getattr(thunk, 'cached', False):
if 'ttl' in data:
kw['_cache_ttl'] = data['ttl']
if 'stale' in data:
kw['_cache_stale'] = data['stale']
return jsonify_result(thunk(*data.get('params', ()), **kw), id=data.get('id'))
except Exception as ex: except Exception as ex:
logger.exception(request.url) logger.exception(request.url)
return jsonify({ 'result': None, 'error': { 'type': ex.__class__.__name__, 'args': ex.args }, 'id': request.json.get('id') }) return jsonify_error(ex, id=request.json.get('id'))
@app.get('/<path:path>') @app.get('/<path:path>')
def cb_static(path): def cb_static(path):

5
rpc.py
View File

@ -5,12 +5,15 @@ import socket
import threading import threading
import asyncio import asyncio
import warnings import warnings
import logging
from collections import namedtuple from collections import namedtuple
from XWBHash import encrypt0 as XWBHash_encrypt from XWBHash import encrypt0 as XWBHash_encrypt
from typing import Any, Union, Sequence from typing import Any, Union, Sequence
logger = logging.getLogger(__name__)
class RPCExc(Exception): pass class RPCExc(Exception): pass
class RPCExcFormat(ValueError, RPCExc): pass class RPCExcFormat(ValueError, RPCExc): pass
class RPCExcAuth(RPCExc): pass class RPCExcAuth(RPCExc): pass
@ -121,6 +124,7 @@ class ClientSync(object):
if (res := rpc_unpack_result(next(self.recv_rpc_msg), encoding=encoding)) != '1': if (res := rpc_unpack_result(next(self.recv_rpc_msg), encoding=encoding)) != '1':
raise RPCExcInvalidResult('XWB CREATE CONTEXT', context[0], res) raise RPCExcInvalidResult('XWB CREATE CONTEXT', context[0], res)
self.context = context[0] self.context = context[0]
logger.warning(f'RPC: {name} [{self.context}] {args}' if context else f'{name} {args}')
send_rpc_msg(self.sock, rpc_pack(name, *args, command=command, envelope=envelope, encoding=encoding)) send_rpc_msg(self.sock, rpc_pack(name, *args, command=command, envelope=envelope, encoding=encoding))
return rpc_unpack_result(next(self.recv_rpc_msg), encoding=encoding) return rpc_unpack_result(next(self.recv_rpc_msg), encoding=encoding)
def __getattr__(self, key: str, commands: set={'TCPConnect'}): def __getattr__(self, key: str, commands: set={'TCPConnect'}):
@ -196,6 +200,7 @@ class ClientAsync(object):
if (res := rpc_unpack_result(await self.arecv_rpc_msg.__anext__(), encoding=encoding)) != '1': if (res := rpc_unpack_result(await self.arecv_rpc_msg.__anext__(), encoding=encoding)) != '1':
raise RPCExcInvalidResult('XWB CREATE CONTEXT', context[0], res) raise RPCExcInvalidResult('XWB CREATE CONTEXT', context[0], res)
self.context = context[0] self.context = context[0]
logger.warning(f'RPC: {name} [{self.context}] {args}' if context else f'{name} {args}')
await asend_rpc_msg(self.writer, rpc_pack(name, *args, command=command, envelope=envelope, encoding=encoding)) await asend_rpc_msg(self.writer, rpc_pack(name, *args, command=command, envelope=envelope, encoding=encoding))
return rpc_unpack_result(await self.arecv_rpc_msg.__anext__(), encoding=encoding) return rpc_unpack_result(await self.arecv_rpc_msg.__anext__(), encoding=encoding)
def __getattr__(self, key: str, commands: set={'TCPConnect'}): def __getattr__(self, key: str, commands: set={'TCPConnect'}):

14
util.py
View File

@ -16,6 +16,15 @@ except ImportError:
from typing import Any, Union, AsyncGenerator, Iterable, Tuple, Callable from typing import Any, Union, AsyncGenerator, Iterable, Tuple, Callable
class Cached(object):
def __init__(self, base, ts=None):
self._base = base
self._ts = ts
def __getattr__(self, key):
return getattr(self._base, key)
def __getitem__(self, key):
return getitem(self._base, key)
class Store(object): class Store(object):
def __init__(self, database: Union[sqlite3.Connection, str]=':memory:', synchronous: bool=None, journal_mode: bool=None, default_factory: Union[Callable, None]=None): def __init__(self, database: Union[sqlite3.Connection, str]=':memory:', synchronous: bool=None, journal_mode: bool=None, default_factory: Union[Callable, None]=None):
self._db = database if isinstance(database, sqlite3.Connection) else sqlite3.connect(database, check_same_thread=False) self._db = database if isinstance(database, sqlite3.Connection) else sqlite3.connect(database, check_same_thread=False)
@ -60,8 +69,8 @@ class Mapping(object):
def get(self, key: Union[str, slice], ttl: float=float('inf'), now: float=0, **kw) -> Any: def get(self, key: Union[str, slice], ttl: float=float('inf'), now: float=0, **kw) -> Any:
if isinstance(key, slice): if isinstance(key, slice):
key, ttl, now = key.start, key.stop, key.step key, ttl, now = key.start, key.stop, key.step
for row in self._store.execute(f'SELECT value FROM "{self._tbl}" WHERE key = ? AND ts > ? LIMIT 1', (key, (now or time.time()) - ttl)): for row in self._store.execute(f'SELECT value, ts FROM "{self._tbl}" WHERE key = ? AND ts > ? LIMIT 1', (key, (now or time.time()) - ttl)):
return loads(row[0]) return Cached(loads(row[0]), row[1])
if 'default' in kw: if 'default' in kw:
return kw['default'] return kw['default']
elif self._store._default_factory is not None: elif self._store._default_factory is not None:
@ -148,6 +157,7 @@ class CacheProxy(object):
return fetch(*args, **kw) return fetch(*args, **kw)
else: else:
return value return value
thunk.cached = True
setattr(self, key, thunk) setattr(self, key, thunk)
return thunk return thunk