Commit f8faed5e authored by Marcel Rieger's avatar Marcel Rieger
Browse files

Start using json api, update ajax and getTemplate methods, start using async instead of Deferred.

parent 14901d21
......@@ -247,10 +247,6 @@ class Netstat(object):
return None
class MessageException(Exception):
pass
class AjaxException(Exception):
def __init__(self, message, code=500):
......
......@@ -7,7 +7,7 @@ from vispa.models.user import User
from vispa.models.workspace import Workspace, WorkspaceState
from vispa.models.preference import VispaPreference, ExtensionPreference
from vispa.models.shortcuts import VispaShortcuts, ExtensionShortcuts
from vispa import MessageException
from vispa import AjaxException
import vispa.workspace
import logging
import json
......@@ -54,7 +54,7 @@ class AjaxController(AbstractController):
if vispa.config("web", "forgot.use", False) and not cherrypy.session.get("is_guest", False):
User.forgot_password(cherrypy.request.db, username)
else:
raise MessageException("Password restoring not allowed!")
raise AjaxException("Password restoring not allowed!")
@cherrypy.expose
@cherrypy.tools.user(on=False)
......@@ -99,7 +99,7 @@ class AjaxController(AbstractController):
@cherrypy.tools.ajax()
def addworkspace(self, name, host, login, key=None, cmd=None):
if not vispa.config("workspace", "add", True):
raise MessageException("No permission to add a new Workspace!")
raise AjaxException("No permission to add a new Workspace!")
db = self.get("db")
user_id = self.get("user_id")
......@@ -109,7 +109,7 @@ class AjaxController(AbstractController):
for workspace in workspaces:
# case insensitive
if name.lower() == workspace.name.lower():
raise MessageException("Workspace '%s' already in use!" % name)
raise AjaxException("Workspace '%s' already in use!" % name)
# add the workspace
workspace = Workspace.add(db, user_id, name, host, login, key=key,
......@@ -120,21 +120,21 @@ class AjaxController(AbstractController):
@cherrypy.tools.ajax()
def deleteworkspace(self, wid):
if not vispa.config("workspace", "add", True):
raise MessageException("No permission to delete a Workspace!")
raise AjaxException("No permission to delete a Workspace!")
db = cherrypy.request.db
# can the user edit the workspace?
workspace = Workspace.get_by_id(db, wid)
if not isinstance(workspace, Workspace):
raise MessageException("Unknown Workspace!")
raise AjaxException("Unknown Workspace!")
if not workspace.can_edit(self.get("user_id")):
raise MessageException("No permission to delete this Workspace!")
raise AjaxException("No permission to delete this Workspace!")
# remove it
success = Workspace.remove(db, wid)
if not success:
raise MessageException("Couldn't remove this Workspace!")
raise AjaxException("Couldn't remove this Workspace!")
@cherrypy.expose
@cherrypy.tools.ajax()
......@@ -146,7 +146,7 @@ class AjaxController(AbstractController):
# is workspace owned by the user?
workspace = Workspace.get_by_id(db, wid)
if not isinstance(workspace, Workspace) or not workspace.has_access(user):
raise MessageException("Unknown Workspace! (id: %s)" % wid)
raise AjaxException("Unknown Workspace! (id: %s)" % wid)
vispa.workspace.connect(workspace, user, db, password)
......@@ -159,7 +159,7 @@ class AjaxController(AbstractController):
# is workspace owned by the user?
workspace = Workspace.get_by_id(db, wid)
if not isinstance(workspace, Workspace) or not workspace.has_access(user):
raise MessageException("Unknown Workspace! (id: %s)" % wid)
raise AjaxException("Unknown Workspace! (id: %s)" % wid)
# disconnect
vispa.workspace.disconnect(workspace)
......
......@@ -6,7 +6,7 @@ import vispa
from vispa.controller import AbstractController
from vispa.socketbus import SocketPublisher, PollingPublisher, add_session, \
USE_SOCKETS, SUBSCRIBERS, POLLING_TIMESTAMPS, get_polling_publisher
from vispa import MessageException
from vispa import AjaxException
from time import time
import json
......
......@@ -10,7 +10,7 @@ from cherrypy.lib import file_generator, cptools, httputil
import cherrypy
import rpyc
from vispa import MessageException
from vispa import AjaxException
from vispa.controller import AbstractController
......@@ -114,13 +114,13 @@ class FSAjaxController(AbstractController):
view_id=self.get('view_id'),
watch_id=watch_id)
if count == -1:
raise MessageException("%s does not exist" % path)
raise AjaxException("%s does not exist" % path)
# instead of raising an exception we return -2 as count to account for
# the fact that the user does not have the permission to read the path
# in the GUI elif count == -2:
# raise MessageException("You do not have rights to read %s." % path)
# raise AjaxException("You do not have rights to read %s." % path)
elif not isinstance(count, int):
raise MessageException(count)
raise AjaxException(count)
else:
return {"count": count}
......@@ -242,7 +242,7 @@ class FSAjaxController(AbstractController):
break
success, msg = fs.save_file_content(filename, data, path=path, force=True, append=append)
if not success:
raise MessageException(msg)
raise AjaxException(msg)
if not append:
append = True
......@@ -310,7 +310,7 @@ class FSAjaxController(AbstractController):
view_id=self.get('view_id'),
watch_id=watch_id)
if err:
raise MessageException(err)
raise AjaxException(err)
return {"success": not err}
@cherrypy.expose
......@@ -324,7 +324,7 @@ class FSAjaxController(AbstractController):
view_id=self.get('view_id'),
watch_id=watch_id)
if err:
raise MessageException(err)
raise AjaxException(err)
return {"success": not err}
@cherrypy.expose
......@@ -345,7 +345,7 @@ class FSAjaxController(AbstractController):
err = fs.set_workspaceini(request)
if err:
raise MessageException(err)
raise AjaxException(err)
return {"success": not err}
@cherrypy.expose
......
......@@ -3,7 +3,7 @@
# imports
import cherrypy
from vispa.controller import AbstractController
from vispa import MessageException
from vispa import AjaxException
class EditorController(AbstractController):
......@@ -21,7 +21,7 @@ class EditorController(AbstractController):
rpc = self.getrpc()
err = rpc.start(cmd, base)
if err:
raise MessageException(err)
raise AjaxException(err)
return {"success": not err}
@cherrypy.expose
......
......@@ -4,7 +4,7 @@
import cherrypy
import vispa.workspace
from vispa.controller import AbstractController
from vispa import MessageException
from vispa import AjaxException
class DummyController(AbstractController):
......@@ -37,7 +37,7 @@ class DummyController(AbstractController):
def failure(self, msg=None):
if msg == None:
msg = "This is a failure message. Something went wrong!"
raise MessageException(msg)
raise AjaxException(msg)
@cherrypy.expose
@cherrypy.tools.workspace(on=False)
......
......@@ -5,7 +5,7 @@ from sqlalchemy import Column, schema
from sqlalchemy.types import Integer, Unicode, DateTime, UnicodeText
from datetime import datetime
from vispa.models import Base, insertion_safe
from vispa import MessageException
from vispa import AjaxException
import json as JSON
_all__ = ["VispaPreference", "ExtensionPreference"]
......@@ -65,7 +65,7 @@ class VispaPreference(Base):
def set_value(session, user_id, section, value, update=True):
safe, key = insertion_safe(section, value)
if not safe:
raise MessageException(
raise AjaxException(
"Couldn't update preference '%s'!" %
section)
# entry already exists?
......@@ -85,7 +85,7 @@ class VispaPreference(Base):
session.add(preference)
session.commit()
if not isinstance(preference, VispaPreference):
raise MessageException(
raise AjaxException(
"Couldn't update preference '%s'!" %
section)
return preference
......@@ -145,7 +145,7 @@ class ExtensionPreference(Base):
def set_value(session, user_id, key, value, update=True):
safe, _key = insertion_safe(key, value)
if not safe:
raise MessageException("Couldn't update preference '%s'!" % key)
raise AjaxException("Couldn't update preference '%s'!" % key)
# entry already exists?
preference = ExtensionPreference.get_by_user_id(
session,
......@@ -163,5 +163,5 @@ class ExtensionPreference(Base):
session.add(preference)
session.commit()
if not isinstance(preference, ExtensionPreference):
raise MessageException("Couldn't update preference '%s'!" % key)
raise AjaxException("Couldn't update preference '%s'!" % key)
return preference
......@@ -64,7 +64,7 @@ class VispaShortcuts(Base):
def set_value(session, user_id, key, value, update=True):
safe, _key = insertion_safe(key, value)
if not safe:
raise MessageException("Couldn't update shortcuts '%s'!" % key)
raise AjaxException("Couldn't update shortcuts '%s'!" % key)
# entry already exists?
all_shortcuts = VispaShortcuts.get_by_user_id(
session,
......@@ -82,7 +82,7 @@ class VispaShortcuts(Base):
session.add(all_shortcuts)
session.commit()
if not isinstance(all_shortcuts, VispaShortcuts):
raise MessageException("Couldn't update shortcuts '%s'!" % key)
raise AjaxException("Couldn't update shortcuts '%s'!" % key)
return all_shortcuts
......@@ -140,7 +140,7 @@ class ExtensionShortcuts(Base):
def set_value(session, user_id, key, value, update=True):
safe, _key = insertion_safe(key, value)
if not safe:
raise MessageException("Couldn't update shortcuts '%s'!" % key)
raise AjaxException("Couldn't update shortcuts '%s'!" % key)
# entry already exists?
all_shortcuts = ExtensionShortcuts.get_by_user_id(
session,
......@@ -158,5 +158,5 @@ class ExtensionShortcuts(Base):
session.add(all_shortcuts)
session.commit()
if not isinstance(all_shortcuts, ExtensionShortcuts):
raise MessageException("Couldn't update shortcuts '%s'!" % key)
raise AjaxException("Couldn't update shortcuts '%s'!" % key)
return all_shortcuts
......@@ -10,7 +10,7 @@ from passlib.hash import sha256_crypt
from sqlalchemy import Column, Table, ForeignKey, func
from sqlalchemy.orm import relationship
from sqlalchemy.types import Unicode, DateTime, Integer, UnicodeText
from vispa import MessageException
from vispa import AjaxException
from vispa.models import Base
import vispa
logger = logging.getLogger(__name__)
......@@ -97,22 +97,22 @@ class User(Base):
@staticmethod
def login(session, username, password):
if username is None:
raise MessageException('Invalid username')
raise AjaxException('Invalid username')
if password is None:
raise MessageException('Invalid password')
raise AjaxException('Invalid password')
user = User.get_by_name(session, username)
if user is None:
user = User.get_by_email(session, username)
if user is None:
logger.info('Unknown user %s' % username)
raise MessageException('Wrong username or password')
raise AjaxException('Wrong username or password')
if not sha256_crypt.verify(password, user.password):
raise MessageException('Wrong username or password')
raise AjaxException('Wrong username or password')
if user.status != User.ACTIVE:
raise MessageException("Account is not active")
raise AjaxException("Account is not active")
vispa.fire_callback("user.login", user)
......@@ -154,11 +154,11 @@ class User(Base):
def register(session, name, email):
# check by name
if User.get_by_name(session, name) is not None:
raise MessageException('Username or mail address already exists!')
raise AjaxException('Username or mail address already exists!')
# check by email
if User.get_by_email(session, email) is not None:
raise MessageException('Username or mail address already exists!')
raise AjaxException('Username or mail address already exists!')
# max users?
max_users = vispa.config('user', 'registration.max_users', 0)
......@@ -182,24 +182,24 @@ class User(Base):
if len(valid_hosts) and host not in valid_hosts:
emailvalid = False
if not emailvalid:
raise MessageException('Please use a valid email address!')
raise AjaxException('Please use a valid email address!')
# check the name
# => length
if len(name) < User.NAME_LENGTH[0] or len(name) > User.NAME_LENGTH[1]:
raise MessageException(
raise AjaxException(
'The length of the username should be between %d and %d chars!' %
tuple(
User.NAME_LENGTH))
# => chars
for char in name:
if char not in User.NAME_CHARS:
raise MessageException(
raise AjaxException(
'The char \'%s\' is not allowed!' %
char)
# => forbidden?
if name.lower() in User.FORBIDDEN_NAMES:
raise MessageException('This username is forbidden!')
raise AjaxException('This username is forbidden!')
# The userdata passed all checks
......@@ -227,9 +227,9 @@ class User(Base):
def set_password(db, hash, password):
user = db.query(User).filter_by(hash=hash).first()
if not isinstance(user, User):
raise MessageException("Invalid hash!")
raise AjaxException("Invalid hash!")
if len(password) < User.MIN_PW_LENGTH:
raise MessageException("Please select a password with a minimum "
raise AjaxException("Please select a password with a minimum "
"length of %s!" % User.MIN_PW_LENGTH)
user.hash = None
user.password = sha256_crypt.encrypt(password)
......@@ -266,14 +266,14 @@ Your Vispa-Team!""" % (name, link)
if not isinstance(user, User):
user = User.get_by_email(db, name_or_mail.lower())
if not user or user.status == User.INACTIVE:
raise MessageException("This is not an active user!")
raise AjaxException("This is not an active user!")
now = datetime.now()
last = user.last_password_reset
delay = User.PASSWORD_RESET_DELAY
if last and (now - last).total_seconds() < delay * 60:
msg = "You only can reset your password every %s minutes!" % delay
raise MessageException(msg)
raise AjaxException(msg)
user.last_password_reset = now
hash = User.generate_hash(32)
......
......@@ -7,7 +7,7 @@ from sqlalchemy.types import Unicode, Integer, DateTime, Boolean, Text,\
UnicodeText
from vispa.models import Base, insertion_safe
from vispa.models.user import User
from vispa import MessageException
from vispa import AjaxException
import json
__all__ = ["Workspace", "WorkspaceState", "WorkspaceConnection"]
......@@ -93,10 +93,10 @@ class Workspace(Base):
user_id = user if not isinstance(user, User) else user.id
if not name:
raise MessageException("No workspace name given")
raise AjaxException("No workspace name given")
safe, msg = insertion_safe(name, host, login, key, command)
if not safe:
raise MessageException(msg)
raise AjaxException(msg)
entries = {
"user_id": user_id,
"name" : name,
......
......@@ -330,6 +330,7 @@ define(["jquery", "emitter", "vispa/messenger"], function($, Emitter, Messenger)
// actual managing functions
attach: function(instance, tabElement, contentElement, // required arguments
instanceToReplace, originatingInstance, focusIndex) { // optional arguments
console.log(arguments);
if (!(instance instanceof UIElement)) throw "Instance needs to be an UIElement";
if (!tabElement) throw "No tab element given";
......
......@@ -164,8 +164,8 @@ define([
}
});
// template (deferred) store
this.templateDfds = {};
// template store
this.templates = {};
// view store
this.views = [];
......@@ -355,28 +355,22 @@ define([
_getTemplate: function(path, callback) {
var self = this;
path = this.url.dynamic(path);
callback = callback || function(err) {};
var present = !!this.templateDfds[path];
var dfd = this.templateDfds[path] = this.templateDfds[path] || $.Deferred();
dfd.done(function(tmpl) {
callback(null, tmpl, dfd);
}).fail(function(err) {
callback(err, null, dfd);
});
if (callback === undefined) {
callback = function(){};
}
if (!present) {
this.GET(path + "?_=" + this.startupTime.getTime()).done(function(res) {
dfd.resolve(res);
}).fail(function(res, status, msg) {
var err = res.responseText || msg;
dfd.reject(err);
if (path in this.templates) {
callback(null, this.templates[path]);
} else {
this.GET(path + "?_=" + this.startupTime.getTime(), function(err, tmpl) {
if (!err) {
self.templates[path] = tmpl;
}
callback(err, tmpl);
});
}
return dfd;
return this;
},
getTemplate: function(path, callback) {
......@@ -522,7 +516,6 @@ define([
History[replace ? "replaceState" : "pushState"](data, title, "?" + fragment);
}
},
applyFragment: function() {
......@@ -572,77 +565,89 @@ define([
instance.focus();
}
this.fragmentData.loadLock = false;
return true;
}
}
return false;
},
ajax: function(method, url, data, callback, json) {
var self = this;
method = method.toUpperCase();
var l = arguments.length;
// at least two arguments and method in [GET, POST]
if (l < 2 || !~["GET", "POST"].indexOf(method)) {
return null;
}
/**
* Ajax wrapper method that represents the core of the vispa ajax system. It should be used
* instead of jQuery's methods since vispa implements the callback-as-last-argument style
* instead of using jQuery's jqXHR/Deferred objects.
*
* @param {string} method - The http method to use. At the moment, only "GET" and "POST" are
* supported.
* @param {string} url - The url of the endpoint to use, relative to the page root.
* @param {object|string} [data={}] - The Data to send. When a string is passed, it is assumed that
* it is an encoded json object and the content type will be set accordingly. When an object
* is passed, it is passed to jQuery's ajax method the normal way.
* @param {function} [callback] - A callback method that is invoked with three parameters: 1.
* an error instance, 2. the value of the "data" attribute of the response, 3. the response
* object itself. When there was an (no) error, the second (first) parameter is null. When an
* error occured before any ajax request could be sent (e.g. wrong arguments), the third
* parameter is null as well.
* @returns {this}
*/
ajax: function(method, url, data, callback) {
var self = this;
var defaultCallback = function(err) {
if (err) {
var msg = err.responseText || err;
self.messenger.alert(msg);
// default arguments
if (callback === undefined) {
if ($.isFunction(data)) {
callback = data;
data = {}
} else {
callback = function(){};
}
};
// argument shifting
if (l == 2) {
data = {};
} else if (l == 3 && $.isFunction(data)) {
callback = data;
data = {};
} else if (l == 4 && !$.isFunction(callback)) {
json = callback;
}
if (!$.isFunction(callback)) {
callback = defaultCallback;
// check the method
var _method = method.toUpperCase();
if (!~["GET", "POST"].indexOf(_method)) {
var err = new Error("invalid method passed to vispa.ajax: '" + _method + "'");
this.logger.warning(err.message);
callback(err);
return this;
}
method = _method;
// build the request
// build the request, extend with common data
var req = {
type: method,
url : url
};
// add common data
var commonData = {
_windowId : this.windowId
url : url,
data: $.extend(true, {}, data, {
_windowId: this.windowId
})
};
if ($.isPlainObject(data)) {
req.data = $.extend(true, {}, data, commonData);
} else {
this.logger.error("'data' needs to be a plain object, is", typeof(data));
return null;
}
if (json) {
req.data = JSON.stringify(data);
// json encoded?
if (typeof(data) == "string") {
req.contentType = "application/json";
}
var onDone = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(null);
callback.apply(null, args);
};
var onFail = function(err) {
callback(err);
};
// perform the request
$.ajax(req).always(function(_, _, jqXHR) {
var res = jqXHR.responseJSON;
if (res === undefined) {
res = {
code : jqXHR.status,
data : jqXHR.responseText,
message: jqXHR.status == 200 ? null : jqXHR.statusText
};
}
return $.ajax(req).done(onDone).fail(onFail);
if (res.code == 200) {
callback(null, res.data, res);
} else {
var err = new Error(res.message);
callback(err, null, res);
}
});
return this;
},
GET: function() {
......