Commit 7087e30c authored by Sebastien Jourdain's avatar Sebastien Jourdain
Browse files

Add server side FileBrowser

This add a new vtkWeb widget to deal with server side browsing.

Change-Id: Id78b12c13eb20b2a64f9b82fa90ebbe02a4fd338
parent 3a2d3f71
......@@ -7,6 +7,7 @@ set(WEB_APPLICATIONS
Cone
GraphLayout
PhylogeneticTree
FileBrowser
)
set(WEB_APPS_DEPENDS)
......
......@@ -13,7 +13,7 @@
<div class="viewport-container">
</div>
<script src="../../lib/js/vtkweb-loader-min.js" load="all-min"></script>
<script src="../../lib/core/vtkweb-loader-min.js" load="all-min"></script>
<script type="text/javascript">
var connection = {
sessionURL: "ws://" + window.location.host + "/ws",
......
r"""
This module is a VTK Web server application.
The following command line illustrate how to use it::
$ vtkpython .../vtk_web_filebrowser.py --data-dir /.../server_directory_to_share
Any VTK Web executable script come with a set of standard arguments that
can be overriden if need be::
--host localhost
Interface on which the HTTP server will listen on.
--port 8080
Port number on which the HTTP server will listen to.
--content /path-to-web-content/
Directory that you want to server as static web content.
By default, this variable is empty which mean that we rely on another server
to deliver the static content and the current process only focus on the
WebSocket connectivity of clients.
--authKey vtk-secret
Secret key that should be provided by the client to allow it to make any
WebSocket communication. The client will assume if none is given that the
server expect "vtk-secret" as secret key.
"""
# import to process args
import sys
import os
# import vtk modules.
import vtk
from vtk.web import server, wamp, protocols
# import annotations
from autobahn.wamp import exportRpc
try:
import argparse
except ImportError:
# since Python 2.6 and earlier don't have argparse, we simply provide
# the source for the same as _argparse and we use it instead.
import _argparse as argparse
# =============================================================================
# Create custom File Opener class to handle clients requests
# =============================================================================
class _WebFileBrowser(wamp.ServerProtocol):
# Application configuration
authKey = "vtkweb-secret"
basedir = ""
def initialize(self):
# Bring used components
self.registerVtkWebProtocol(protocols.vtkWebFileBrowser(_WebFileBrowser.basedir, "Home"))
# Update authentication key to use
self.updateSecret(_WebFileBrowser.authKey)
# =============================================================================
# Main: Parse args and start server
# =============================================================================
if __name__ == "__main__":
# Create argument parser
parser = argparse.ArgumentParser(description="VTK/Web FileBrowser web-application")
# Add default arguments
server.add_arguments(parser)
# Add local arguments
parser.add_argument("--data-dir", help="Base directory to list", dest="basedir", default=".")
# Exctract arguments
args = parser.parse_args()
# Configure our current application
_WebFileBrowser.authKey = args.authKey
_WebFileBrowser.basedir = args.basedir
# Start server
server.start_webserver(options=args, protocol=_WebFileBrowser)
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body class="page" onbeforeunload="stop()" onunload="stop()">
<div class="browser"></div>
<script src="../../lib/core/vtkweb-loader.js" load="all-min, filebrowser"></script>
<script type="text/javascript">
var connection = {
sessionURL: "ws://" + window.location.host + "/ws",
name: "WebFileBrowser",
description: "Simple VTK Web demo application",
application: "fileBrowser"
},
// Connect to remote server
vtkWeb.connect(connection, function(serverConnection) {
connection = serverConnection;
// Create file browser widget
connection.session.call('vtk:listServerDirectory','.').then(function(files){
$('.browser').fileBrowser({data: [files], session: connection.session}).bind('file-click directory-click directory-not-found', function(e){
console.log(e.type + " click: " + e.name + " - path: " + e.path );
});;
});
}, function(code, reason) {
alert(reason);
});
// Method call at exit time
function stop() {
if(false && connection.session) {
connection.session.call('vtk:exit');
connection.session.close();
connection.session = null;
}
}
</script>
</body>
</html>
html, body, div, span, h1, h2, h3, h4, p, a, big, em, font, img, s, small, strong,
tt, var, b,u,i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, table,
tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
font-family: Arial,sans-serif; font-size: 1em;
}
.browser {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
.browser ul[type=path] {
background: #cccccc;
border-bottom: solid 1px black;
}
\ No newline at end of file
......@@ -11,4 +11,5 @@
| ---------------- | ---------------------------- | ------------------------- |
| Cone | vtk_web_cone.py | |
| PhylogeneticTree | vtk_web_phylogenetic_tree.py | --tree, --table |
| FileBrowser | vtk_web_filebrowser.py | --data-dir |
| --------------------------------------------------------------------------- |
......@@ -61,10 +61,10 @@
],
"filebrowser": [
"ext/pure/pure.min.js",
"lib/widgets/vtkweb-widget-filebrowser.js",
"lib/widgets/vtkweb-widget-filebrowser.tpl",
"lib/widgets/vtkweb-widget-filebrowser.css"
],
"lib/widgets/FileBrowser/vtkweb-widget-filebrowser.js",
"lib/widgets/FileBrowser/vtkweb-widget-filebrowser.tpl",
"lib/widgets/FileBrowser/vtkweb-widget-filebrowser.css"
]
},
modules = [],
script = document.getElementsByTagName("script")[document.getElementsByTagName("script").length - 1],
......@@ -85,6 +85,48 @@
document.write('<script src="' + url + '"></script>');
}
// ---------------------------------------------------------------------
function loadTemplate(url) {
var templates = document.getElementById("vtk-templates");
if(templates === null) {
templates = document.createElement("div");
templates.setAttribute("style", "display: none;");
templates.setAttribute("id", "vtk-templates");
document.getElementsByTagName("body")[0].appendChild(templates);
}
// Fetch template and append to vtk-templates
var request = makeHttpObject();
request.open("GET", url, true);
request.send(null);
request.onreadystatechange = function() {
if (request.readyState == 4) {
var content = templates.innerHTML;
content += request.responseText;
templates.innerHTML = content;
}
};
}
// ---------------------------------------------------------------------
function makeHttpObject() {
try {
return new XMLHttpRequest();
}
catch (error) {}
try {
return new ActiveXObject("Msxml2.XMLHTTP");
}
catch (error) {}
try {
return new ActiveXObject("Microsoft.XMLHTTP");
}
catch (error) {}
throw new Error("Could not create HTTP request object.");
}
// ---------------------------------------------------------------------
function _endWith(string, end) {
return string.lastIndexOf(end) === (string.length - end.length);
......@@ -97,7 +139,7 @@
} else if(_endWith(url, ".css")) {
loadCss(url);
} else if(_endWith(url, ".tpl")) {
// template to load
loadTemplate(url);
}
}
......@@ -135,7 +177,7 @@
// ---------------------------------------------------------------------
// Extract basePath
// ---------------------------------------------------------------------
var lastSlashIndex = script.getAttribute("src").lastIndexOf('lib/js/vtkweb-loader');
var lastSlashIndex = script.getAttribute("src").lastIndexOf('lib/core/vtkweb-loader');
if(lastSlashIndex != -1) {
basePath = script.getAttribute("src").substr(0, lastSlashIndex);
}
......
......@@ -11,7 +11,7 @@
<body>
<!-- This should be automatically filled by the vtk loader : BEGUIN -->
<div class='vtk-templates' style="display: none;">
<div id='vtk-templates' style="display: none;">
<div class='vtkweb-widget-filebrowser'>
<div>
<div class="vtk-directory">
......
......@@ -11,7 +11,6 @@
.vtk-directory ul[type=path] {
list-style: none;
border-bottom: solid 1px #cccccc;
}
.vtk-directory li {
......@@ -47,4 +46,8 @@
padding-left: 2px;
position: relative;
top: 4px;
}
.vtk-directory .action {
cursor: pointer;
}
\ No newline at end of file
......@@ -52,7 +52,6 @@
// Compile template only once
if(fileBrowserGenerator === null) {
template = $(opts.template);
console.log(template);
fileBrowserGenerator = template.compile(directives);
}
......@@ -60,6 +59,8 @@
var me = $(this).empty().addClass('vtk-filebrowser'),
container = $('<div/>');
me.append(container);
me.data('file-list', opts.data);
me.data('session', opts.session);
// Generate HTML
container.render(opts.data, fileBrowserGenerator);
......@@ -69,9 +70,25 @@
});
};
$.fn.updateFileBrowser = function(activeDirectory) {
return this.each(function() {
var me = $(this).empty(),
data = me.data('file-list'),
container = $('<div/>');
me.append(container);
// Generate HTML
container.render(data, fileBrowserGenerator);
// Initialize pipelineBrowser (Visibility + listeners)
initializeListener(me, activeDirectory);
});
};
$.fn.fileBrowser.defaults = {
template: ".vtk-templates > .vtkweb-widget-filebrowser > div",
root: 'Root',
template: "#vtk-templates > .vtkweb-widget-filebrowser > div",
session: null,
data: [
{ 'label': 'Root', 'path': ['Root'], 'files': [
{'label': 'can.ex2', 'size': 2345},
......@@ -130,18 +147,18 @@
// =======================================================================
function initializeListener(container) {
function initializeListener(container, activePath) {
$('.action', container).click(function(){
var me = $(this), item = $('div', me), pathStr = me.closest('.vtk-directory').attr('path'), type = me.closest('ul').attr('type');
if(type === 'path') {
// Find out the panel to show
var newPath = pathToStr(strToPath(pathStr).slice(0, me.index() + 1));
var newPath = pathToStr(strToPath(pathStr).slice(0, me.index() + 1)),
selector = '.vtk-directory[path="' + newPath + '"]';
var newActive = $(selector , container).addClass('active');
if(newActive.length === 1) {
$('.vtk-directory', container).removeClass('active');
newActive.addClass('active');
$('.vtk-directory', container).removeClass('active');
newActive.addClass('active');
}
} else if(type === 'dir') {
// Swicth active panel
......@@ -156,6 +173,14 @@
name: me.text()
});
} else {
if(container.data('session')) {
var relativePath = (pathStr + '/' + me.text());
container.data('session').call('vtk:listServerDirectory', relativePath.substring(1)).then(function(newFiles){
container.data('file-list').push(newFiles);
container.updateFileBrowser(relativePath);
});
}
container.trigger({
type: 'directory-not-found',
path: pathStr,
......@@ -169,7 +194,12 @@
name: me.text()
});
}
})
});
if(activePath) {
$('.vtk-directory',container).removeClass('active');
$('.vtk-directory[path="' + activePath + '"]',container).addClass('active');
}
}
}(jQuery));
\ No newline at end of file
......@@ -4,11 +4,7 @@ very specific web application.
"""
from time import time
import inspect
import logging
import sys
import traceback
import types
import os, sys, logging, types, inspect, traceback, logging, re
# import RPC annotation
from autobahn.wamp import exportRpc
......@@ -240,3 +236,44 @@ class vtkWebViewPortGeometryDelivery(vtkWebProtocol):
view = self.getView(view_id)
data = self.getApplication().GetWebGLBinaryData(view, str(object_id), part-1)
return data
# =============================================================================
#
# Provide File/Directory listing
#
# =============================================================================
class vtkWebFileBrowser(vtkWebProtocol):
def __init__(self, basePath, name, excludeRegex=r"^\.|~$|^\$"):
"""
Configure the way the WebFile browser will expose the server content.
- basePath: specify the base directory that we should start with
- name: Name of that base directory that will show up on the web
- excludeRegex: Regular expression of what should be excluded from the list of files/directories
"""
self.baseDirectory = basePath
self.rootName = name
self.pattern = re.compile(excludeRegex)
@exportRpc("listServerDirectory")
def listServerDirectory(self, relativeDir='.'):
"""
RPC Callback to list a server directory relative to the basePath
provided at start-up.
"""
path = [ self.rootName ]
if len(relativeDir) > len(self.rootName):
relativeDir = relativeDir[len(self.rootName)+1:]
path += relativeDir.replace('\\','/').split('/')
currentPath = os.path.join(self.baseDirectory, relativeDir)
result = { 'label': relativeDir, 'files': [], 'dirs': [], 'path': path}
if relativeDir == '.':
result['label'] = self.rootName
for file in os.listdir(currentPath):
if os.path.isfile(os.path.join(currentPath, file)) and not re.search(self.pattern, file):
result['files'].append({'label': file, 'size': -1})
elif os.path.isdir(os.path.join(currentPath, file)) and not re.search(self.pattern, file):
result['dirs'].append(file)
return result
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment