Skip to content
This repository was archived by the owner on Nov 25, 2020. It is now read-only.

Commit 07b28af

Browse files
committed
Add a MetaCacheService on the client side, caching Shared Data and Activity Feed with clever invalidation rules. Keep in memory for the moment, always clearing on workspace switch (and thus on logout).
1 parent c5ffd5f commit 07b28af

File tree

6 files changed

+225
-12
lines changed

6 files changed

+225
-12
lines changed

core/src/plugins/action.share/res/react/model/ShareModel.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,15 +607,28 @@
607607
load(){
608608
if(this._status == 'loading') return;
609609
this._setStatus('loading');
610-
ShareModel.loadSharedElementData(this._node, function(transport){
610+
let cacheService = MetaCacheService.getInstance();
611+
cacheService.registerMetaStream('action.share', MetaCacheService.EXPIRATION_LOCAL_NODE);
612+
613+
let remoteLoader = function(transport){
611614
if(transport.responseJSON){
612615
this._data = transport.responseJSON;
613616
this._pendingData = {};
614617
this._setStatus('idle');
618+
return this._data;
615619
}else if(transport.responseXML && XMLUtils.XPathGetSingleNodeText(transport.responseXML, '//message[@type="ERROR"]')){
616620
this._setStatus('error');
621+
return null;
617622
}
618-
}.bind(this));
623+
}.bind(this);
624+
625+
let cacheLoader = function(data){
626+
this._data = data;
627+
this._pendingData = {};
628+
this._setStatus('idle');
629+
}.bind(this);
630+
631+
cacheService.metaForNode('action.share', this._node, ShareModel.loadSharedElementData, remoteLoader, cacheLoader);
619632
}
620633

621634
save(){

core/src/plugins/core.notifications/class.NotificationLoader.js

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,32 @@ Class.create("NotificationLoader", {
295295
protoMenu.correctWindowClipping(protoMenu.container, offset, dim);
296296
},
297297

298+
clearMetaCacheForPath: function(path){
299+
var parts = path.split("/");
300+
while(parts.length){
301+
var newPath = parts.join("/");
302+
if(!newPath) newPath = '/';
303+
MetaCacheService.getInstance().clearMetaStreamKeys("files.activity" + newPath);
304+
parts.pop();
305+
}
306+
},
307+
298308
loadInfoPanel : function(container, node){
309+
if(!NotificationLoader.LOADED){
310+
311+
pydio.getContextHolder().observe("server_update", function(pathes){
312+
// Clear all caches for all pathes.
313+
for(var i = 0; i < pathes.length ; i++){
314+
NotificationLoader.prototype.clearMetaCacheForPath(pathes[i]);
315+
if(NotificationLoader.IPANEL_FETCHPANE && NotificationLoader.IPANEL_FETCHPANE_PATH && pathes[i].indexOf(NotificationLoader.IPANEL_FETCHPANE_PATH) === 0){
316+
NotificationLoader.IPANEL_FETCHPANE.reloadDataModel();
317+
}
318+
}
319+
});
320+
321+
NotificationLoader.LOADED = true;
322+
}
323+
299324
var label= MessageHash['notification_center.'+(node.isLeaf()?'11': (node.isRoot()?'9': '10'))];
300325
var mainContainer = container.down("#ajxp_activity_panel");
301326
mainContainer.addClassName("infopanel_loading");
@@ -306,7 +331,8 @@ Class.create("NotificationLoader", {
306331
var timer = NotificationLoader.prototype.loaderTimer;
307332
if(timer) window.clearTimeout(timer);
308333

309-
var fRp = new FetchedResultPane(resultPane, {
334+
NotificationLoader.IPANEL_FETCHPANE_PATH = node.getPath();
335+
NotificationLoader.IPANEL_FETCHPANE = new FetchedResultPane(resultPane, {
310336
"fit":"content",
311337
"replaceScroller": false,
312338
"columnsDef":[
@@ -325,10 +351,14 @@ Class.create("NotificationLoader", {
325351
"limit":(node.isLeaf() || node.isRoot() ? 18 : 4),
326352
"path":(node.isLeaf() || node.isRoot()?node.getPath():node.getPath()+'/'),
327353
"merge_description":"true",
328-
"description_as_label":node.isLeaf()?"true":"false"
354+
"description_as_label":node.isLeaf()?"true":"false",
355+
"cache_service":{
356+
"metaStreamName":"files.activity" + node.getPath(),
357+
"expirationPolicy":MetaCacheService.EXPIRATION_MANUAL_TRIGGER
358+
}
329359
}});
330360
var pane = container.up('[ajxpClass="InfoPanel"]');
331-
fRp._rootNode.observe("loaded", function(){
361+
NotificationLoader.IPANEL_FETCHPANE._rootNode.observe("loaded", function(){
332362
if(!mainContainer.hasClassName("infopanel_loading_finished")){
333363
mainContainer.addClassName("infopanel_loading_finished");
334364
}
@@ -338,7 +368,7 @@ Class.create("NotificationLoader", {
338368
},0.2);
339369
}
340370
if(container.down('#ajxp_activity_panel > div.panelHeader')){
341-
if(!fRp._rootNode.getChildren().size){
371+
if(!NotificationLoader.IPANEL_FETCHPANE._rootNode.getChildren().size){
342372
container.down('#ajxp_activity_panel > div.panelHeader').hide();
343373
}else{
344374
container.down('#ajxp_activity_panel > div.panelHeader').show();
@@ -347,7 +377,7 @@ Class.create("NotificationLoader", {
347377
});
348378
// fRp.showElement(true);
349379
NotificationLoader.prototype.loaderTimer = window.setTimeout(function(){
350-
fRp.showElement(true);
380+
NotificationLoader.IPANEL_FETCHPANE.showElement(true);
351381
}, 0.5);
352382

353383
},

core/src/plugins/gui.ajax/Gruntfile.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var gui_ajax_core = [
4747
'res/js/core/model/BackgroundTasksManager.js',
4848
'res/js/core/http/PydioApi.js',
4949
'res/js/core/http/PydioUsersApi.js',
50+
'res/js/core/http/MetaCacheService.js',
5051
'res/js/core/model/Action.js',
5152
'res/js/core/model/Controller.js',
5253
'res/js/core/model/PydioDataModel.js',
@@ -179,6 +180,16 @@ module.exports = function(grunt) {
179180
}
180181
},
181182
watch: {
183+
es6 : {
184+
files:[
185+
'res/js/es6/*.es6',
186+
'res/js/es6/**/*.es6'
187+
],
188+
tasks:['babel:dist'],
189+
options:{
190+
spawn:false
191+
}
192+
},
182193
core: {
183194
files: gui_ajax_core,
184195
tasks: ['babel:dist', 'uglify:js'],

core/src/plugins/gui.ajax/res/js/ajaxplorer_core.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ js/core/model/EmptyNodeProvider.js
1717
js/core/model/Repository.js
1818
js/core/model/BackgroundTasksManager.js
1919
js/core/http/PydioApi.js
20+
js/core/http/MetaCacheService.js
2021
js/core/model/Action.js
2122
js/core/model/Controller.js
2223
js/core/model/PydioDataModel.js
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2007-2016 Charles du Jeu - Abstrium SAS <team (at) pyd.io>
3+
* This file is part of Pydio.
4+
*
5+
* Pydio is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Pydio is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with Pydio. If not, see <http://www.gnu.org/licenses/>.
17+
*
18+
* The latest code can be found at <http://pyd.io/>.
19+
*/
20+
/**
21+
* API Client
22+
*/
23+
class MetaCacheService extends Observable{
24+
25+
static getInstance(){
26+
if(!MetaCacheService.INSTANCE){
27+
MetaCacheService.INSTANCE = new MetaCacheService();
28+
}
29+
return MetaCacheService.INSTANCE;
30+
}
31+
32+
constructor(){
33+
super();
34+
this._streams = new Map();
35+
}
36+
37+
hasKey(streamName, keyName){
38+
if(!this._streams.get(streamName)){
39+
throw Error('Stream ' + streamName + ' not registered, please register first');
40+
}
41+
return this._streams.get(streamName).get('data').has(keyName);
42+
}
43+
44+
getByKey(streamName, keyName){
45+
if(!this._streams.get(streamName)){
46+
throw Error('Stream ' + streamName + ' not registered, please register first');
47+
}
48+
return this._streams.get(streamName).get('data').get(keyName);
49+
}
50+
51+
deleteKey(streamName, keyName){
52+
if(!this._streams.get(streamName)){
53+
throw Error('Stream ' + streamName + ' not registered, please register first');
54+
}
55+
this._streams.get(streamName).get('data').delete(keyName);
56+
}
57+
58+
setKey(streamName, keyName, value){
59+
if(!this._streams.get(streamName)){
60+
throw Error('Stream ' + streamName + ' not registered, please register first');
61+
}
62+
this._streams.get(streamName).get('data').set(keyName, value);
63+
}
64+
65+
clearMetaStreamKeys(streamName){
66+
if(this._streams.has(streamName)){
67+
this._streams.get(streamName).set('data', new Map());
68+
}
69+
}
70+
71+
registerMetaStream(streamName, expirationPolicy){
72+
if(this._streams.get(streamName)){
73+
return;
74+
}
75+
let data = new Map();
76+
data.set('expirationPolicy', expirationPolicy);
77+
data.set('data', new Map());
78+
this._streams.set(streamName, data);
79+
pydio.observe("repository_list_refreshed", function(){
80+
// Always keep the cache at workspace scope
81+
this._streams.delete(streamName);
82+
}.bind(this));
83+
}
84+
85+
metaForNode(streamName, ajxpNode, loaderCallback, remoteParser, cacheLoader){
86+
if(!this._streams.has(streamName)){
87+
throw new Error('Cannot find meta stream ' + streamName + ', please register it before using it');
88+
}
89+
let def = this._streams.get(streamName);
90+
let key = ajxpNode.getPath();
91+
let expirationPolicy = def.get('expirationPolicy');
92+
if(def.get('data').has(key)){
93+
cacheLoader(def.get('data').get(key));
94+
}else{
95+
let clearValueObserver = function(){
96+
def.get('data').delete(key);
97+
}.bind(this);
98+
99+
// Cache response if success
100+
let cacheCallback = function(transport){
101+
let newData = remoteParser(transport);
102+
if(newData !== null){
103+
def.get('data').set(key, newData);
104+
if(expirationPolicy == MetaCacheService.EXPIRATION_LOCAL_NODE){
105+
ajxpNode.observeOnce("node_removed", clearValueObserver);
106+
ajxpNode.observeOnce("node_replaced", clearValueObserver);
107+
}
108+
}
109+
};
110+
loaderCallback(ajxpNode, cacheCallback);
111+
}
112+
}
113+
114+
invalidateMetaForKeys(streamName, keyPattern){
115+
if(!this._streams.has(streamName)){
116+
throw new Error('Cannot find meta stream ' + streamName + ', please register it before using it');
117+
}
118+
let data = this._streams.get(streamName).get('data');
119+
data.forEach(function(value, key){
120+
if(key.match(keyPattern)){
121+
data.delete(key);
122+
}
123+
});
124+
}
125+
126+
}
127+
128+
MetaCacheService.EXPIRATION_LOCAL_NODE = 'LOCAL_NODE';
129+
MetaCacheService.EXPIRATION_MANUAL_TRIGGER = 'MANUAL_TRIGGER';

core/src/plugins/gui.ajax/res/js/es6/model/RemoteNodeProvider.es6

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ class RemoteNodeProvider{
4343
this.discrete = true;
4444
this.properties.delete('connexion_discrete');
4545
}
46+
if(this.properties && this.properties.has('cache_service')){
47+
this.cacheService = this.properties.get('cache_service');
48+
this.properties.delete('cache_service');
49+
MetaCacheService.getInstance().registerMetaStream(
50+
this.cacheService['metaStreamName'],
51+
this.cacheService['expirationPolicy']
52+
);
53+
}
4654
}
4755

4856
/**
@@ -85,10 +93,22 @@ class RemoteNodeProvider{
8593
if(optionalParameters){
8694
params = LangUtils.objectMerge(params, optionalParameters);
8795
}
88-
var complete = function (transport){
96+
let parser = function (transport){
8997
this.parseNodes(node, transport, nodeCallback, childCallback);
98+
return node;
9099
}.bind(this);
91-
PydioApi.getClient().request(params, complete, null, {discrete:this.discrete});
100+
if(this.cacheService){
101+
let loader = function(ajxpNode, cacheCallback){
102+
PydioApi.getClient().request(params, cacheCallback, null, {discrete:this.discrete});
103+
}.bind(this);
104+
let cacheLoader = function(newNode){
105+
node.replaceBy(newNode);
106+
nodeCallback(node);
107+
}.bind(this);
108+
MetaCacheService.getInstance().metaForNode(this.cacheService['metaStreamName'], node, loader, parser, cacheLoader);
109+
}else{
110+
PydioApi.getClient().request(params, parser, null, {discrete:this.discrete});
111+
}
92112
}
93113

94114
/**
@@ -242,28 +262,37 @@ class RemoteNodeProvider{
242262
}
243263

244264
parseAjxpNodesDiffs(xmlElement, targetDataModel, setContextChildrenSelected=false){
245-
var removes = XMLUtils.XPathSelectNodes(xmlElement, "remove/tree");
246-
var adds = XMLUtils.XPathSelectNodes(xmlElement, "add/tree");
247-
var updates = XMLUtils.XPathSelectNodes(xmlElement, "update/tree");
265+
let removes = XMLUtils.XPathSelectNodes(xmlElement, "remove/tree");
266+
let adds = XMLUtils.XPathSelectNodes(xmlElement, "add/tree");
267+
let updates = XMLUtils.XPathSelectNodes(xmlElement, "update/tree");
268+
let notifyServerChange = [];
248269
if(removes && removes.length){
249270
removes.forEach(function(r){
250271
var p = r.getAttribute("filename");
251272
var imTime = parseInt(r.getAttribute("ajxp_im_time"));
252273
targetDataModel.removeNodeByPath(p, imTime);
274+
notifyServerChange.push(p);
253275
});
254276
}
255277
if(adds && adds.length && targetDataModel.getAjxpNodeProvider().parseAjxpNode){
256278
adds.forEach(function(tree){
257279
var newNode = targetDataModel.getAjxpNodeProvider().parseAjxpNode(tree);
258280
targetDataModel.addNode(newNode, setContextChildrenSelected);
281+
notifyServerChange.push(newNode.getPath());
259282
});
260283
}
261284
if(updates && updates.length && targetDataModel.getAjxpNodeProvider().parseAjxpNode){
262285
updates.forEach(function(tree){
263286
var newNode = targetDataModel.getAjxpNodeProvider().parseAjxpNode(tree);
287+
let original = newNode.getMetadata().get("original_path");
264288
targetDataModel.updateNode(newNode, setContextChildrenSelected);
289+
notifyServerChange.push(newNode.getPath());
290+
if(original) notifyServerChange.push(original);
265291
});
266292
}
293+
if(notifyServerChange.length){
294+
targetDataModel.notify("server_update", notifyServerChange);
295+
}
267296
}
268297

269298
/**

0 commit comments

Comments
 (0)