fix #1 introduce the /button API to control the workflow with a single big red button
This commit is contained in:
parent
d4cd19cca4
commit
1576411103
6 changed files with 267 additions and 63 deletions
|
@ -36,7 +36,7 @@ Some browsers require HTTPS, to allow access to the webcam. In the *ssl/* direc
|
||||||
|
|
||||||
# License and copyright
|
# License and copyright
|
||||||
|
|
||||||
Copyright 2018 Davide Alberani <da@mimante.net>
|
Copyright 2018-2019 Davide Alberani <da@mimante.net>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
6
static/css/iziToast.min.css
vendored
Normal file
6
static/css/iziToast.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -6,20 +6,22 @@
|
||||||
<link rel="icon" type="image/png" href="/static/images/sb-favicon.png">
|
<link rel="icon" type="image/png" href="/static/images/sb-favicon.png">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/materialize-icons.css">
|
<link rel="stylesheet" type="text/css" href="/static/css/materialize-icons.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/materialize.min.css">
|
<link rel="stylesheet" type="text/css" href="/static/css/materialize.min.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/iziToast.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/sb.css">
|
<link rel="stylesheet" type="text/css" href="/static/css/sb.css">
|
||||||
<script type="text/javascript" src="/static/js/materialize.min.js"></script>
|
<script type="text/javascript" src="/static/js/materialize.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/iziToast.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/sb.js"></script>
|
<script type="text/javascript" src="/static/js/sb.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main" class="container">
|
<div id="main" class="container">
|
||||||
<div id="header" class="row">
|
<div id="header" class="row center-align">
|
||||||
<h1>toot-my-t-shirt</h1>
|
<h1>toot-my-t-shirt</h1>
|
||||||
Share the love for your geek t-shirt.
|
Share the love for your geek t-shirt.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="button-containers" class="row">
|
<div id="button-containers" class="row center-align">
|
||||||
<button id="take-photo-btn" class="btn waves-effect waves-light disabled" name="take-photo-btn" onClick="takePhoto()">
|
<button id="take-photo-btn" class="btn waves-effect waves-light disabled" name="take-photo-btn" onClick="takePhoto()">
|
||||||
<i class="material-icons left">camera</i>Take photo <small>(2 secs delay)</small>
|
<i class="material-icons left">camera</i>Take photo <small>(1 sec delay)</small>
|
||||||
</button>
|
</button>
|
||||||
<button id="send-photo-btn" class="btn waves-effect waves-light disabled" name="send-photo-btn" onClick="sendPhoto()">
|
<button id="send-photo-btn" class="btn waves-effect waves-light disabled" name="send-photo-btn" onClick="sendPhoto()">
|
||||||
<i class="material-icons left">share</i>Share photo
|
<i class="material-icons left">share</i>Share photo
|
||||||
|
@ -32,10 +34,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row center-align">
|
||||||
<div id="sb-message">
|
|
||||||
<span id="sb-message-text">will be gone in few seconds! <span id="sb-countdown"> </span></span>
|
|
||||||
</div>
|
|
||||||
<div id="canvas-container">
|
<div id="canvas-container">
|
||||||
<video id="sb-video" autoplay="true" muted="muted"></video>
|
<video id="sb-video" autoplay="true" muted="muted"></video>
|
||||||
<canvas id="sb-canvas"></canvas>
|
<canvas id="sb-canvas"></canvas>
|
||||||
|
|
6
static/js/iziToast.min.js
vendored
Normal file
6
static/js/iziToast.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
208
static/js/sb.js
208
static/js/sb.js
|
@ -1,4 +1,4 @@
|
||||||
var countdown = {
|
var Countdown = {
|
||||||
_timeout: null,
|
_timeout: null,
|
||||||
_stepCb: null,
|
_stepCb: null,
|
||||||
_timeoutCb: null,
|
_timeoutCb: null,
|
||||||
|
@ -7,48 +7,89 @@ var countdown = {
|
||||||
_initial_seconds: 5,
|
_initial_seconds: 5,
|
||||||
|
|
||||||
start: function(seconds, timeoutCb, stepCb) {
|
start: function(seconds, timeoutCb, stepCb) {
|
||||||
countdown.stop();
|
Countdown.stop();
|
||||||
countdown.seconds = countdown._initial_seconds = seconds || 5;
|
Countdown.seconds = Countdown._initial_seconds = seconds || 5;
|
||||||
countdown._timeoutCb = timeoutCb || countdown._timeoutCb;
|
Countdown._timeoutCb = timeoutCb || Countdown._timeoutCb;
|
||||||
countdown._stepCb = stepCb || countdown._stepCb;
|
Countdown._stepCb = stepCb || Countdown._stepCb;
|
||||||
countdown.running = true;
|
Countdown.running = true;
|
||||||
countdown._step();
|
Countdown._step();
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: function() {
|
stop: function() {
|
||||||
if (countdown._timeout) {
|
if (Countdown._timeout) {
|
||||||
window.clearTimeout(countdown._timeout);
|
window.clearTimeout(Countdown._timeout);
|
||||||
}
|
}
|
||||||
countdown.running = false;
|
Countdown.running = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
restart: function() {
|
restart: function() {
|
||||||
countdown.start(countdown._initial_seconds);
|
Countdown.start(Countdown._initial_seconds);
|
||||||
},
|
},
|
||||||
|
|
||||||
_step: function() {
|
_step: function() {
|
||||||
if (countdown._stepCb) {
|
if (Countdown._stepCb) {
|
||||||
countdown._stepCb();
|
Countdown._stepCb();
|
||||||
}
|
}
|
||||||
if (countdown.seconds === 0) {
|
if (Countdown.seconds === 0) {
|
||||||
if (countdown._timeoutCb) {
|
if (Countdown._timeoutCb) {
|
||||||
countdown._timeoutCb();
|
Countdown._timeoutCb();
|
||||||
}
|
}
|
||||||
countdown.stop();
|
Countdown.stop();
|
||||||
} else {
|
} else {
|
||||||
countdown._decrement();
|
Countdown._decrement();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_decrement: function() {
|
_decrement: function() {
|
||||||
countdown.seconds = countdown.seconds - 1;
|
Countdown.seconds = Countdown.seconds - 1;
|
||||||
countdown._timeout = window.setTimeout(function() {
|
Countdown._timeout = window.setTimeout(function() {
|
||||||
countdown._step();
|
Countdown._step();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function uuidv4() {
|
||||||
|
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||||
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var UUID = uuidv4();
|
||||||
|
|
||||||
|
function getWSPath() {
|
||||||
|
var loc = window.location, new_uri;
|
||||||
|
if (loc.protocol === "https:") {
|
||||||
|
new_uri = "wss:";
|
||||||
|
} else {
|
||||||
|
new_uri = "ws:";
|
||||||
|
}
|
||||||
|
new_uri += "//" + loc.host + "/ws?uuid=" + UUID;
|
||||||
|
return new_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var WS = new WebSocket(getWSPath(), ['soap', 'xmpp']);
|
||||||
|
|
||||||
|
WS.onerror = function(error) {
|
||||||
|
console.log('WebSocket Error ' + error);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
WS.onmessage = function(e) {
|
||||||
|
console.log("received message on websocket: " + e.data);
|
||||||
|
var jdata = JSON.parse(e.data);
|
||||||
|
if (!(jdata && jdata.source == "button" && jdata.action == "clicked")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Countdown.running) {
|
||||||
|
takePhoto("press again to publish!");
|
||||||
|
} else {
|
||||||
|
sendPhoto();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
function runCamera(stream) {
|
function runCamera(stream) {
|
||||||
console.log("initialize the camera");
|
console.log("initialize the camera");
|
||||||
var video = document.querySelector("#sb-video");
|
var video = document.querySelector("#sb-video");
|
||||||
|
@ -62,8 +103,6 @@ function runCamera(stream) {
|
||||||
|
|
||||||
|
|
||||||
function sendData(data) {
|
function sendData(data) {
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
var boundary = "youarenotsupposedtolookatthis";
|
|
||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
var msg = "";
|
var msg = "";
|
||||||
formData.append("selfie", new Blob([data]), "selfie.jpeg");
|
formData.append("selfie", new Blob([data]), "selfie.jpeg");
|
||||||
|
@ -72,9 +111,18 @@ function sendData(data) {
|
||||||
body: formData
|
body: formData
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
msg = "something went wrong sending the data: " + response.status;
|
msg = response.status;
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
M.toast({"html": msg});
|
iziToast.error({
|
||||||
|
"title": "😭 something wrong sending the data 😭",
|
||||||
|
"message": msg,
|
||||||
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
cancelPhoto();
|
cancelPhoto();
|
||||||
return response.json();
|
return response.json();
|
||||||
|
@ -84,19 +132,42 @@ function sendData(data) {
|
||||||
if (json && json.success) {
|
if (json && json.success) {
|
||||||
msg = "❤ ❤ ❤ photo sent successfully! ❤ ❤ ❤";
|
msg = "❤ ❤ ❤ photo sent successfully! ❤ ❤ ❤";
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
M.toast({"html": msg});
|
iziToast.destroy();
|
||||||
|
iziToast.success({
|
||||||
|
"title": msg,
|
||||||
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
msg = "😭 😭 😭 something wrong on the backend 😭 😭 😭";
|
msg = json.message;
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
M.toast({"html": msg});
|
iziToast.error({
|
||||||
msg = "the server says: " + json.message;
|
"title": "😭 backend error 😭",
|
||||||
console.log(msg);
|
"message": msg,
|
||||||
M.toast({"html": msg});
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
msg = "something went wrong connecting to server: " + err;
|
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
M.toast({"html": msg});
|
iziToast.error({
|
||||||
|
"title": "😭 error connecting to the server 😭",
|
||||||
|
"message": err,
|
||||||
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
cancelPhoto();
|
cancelPhoto();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -104,19 +175,12 @@ function sendData(data) {
|
||||||
|
|
||||||
function cancelPhoto() {
|
function cancelPhoto() {
|
||||||
console.log("cancel photo");
|
console.log("cancel photo");
|
||||||
document.querySelector("#sb-message").style.visibility = "hidden";
|
|
||||||
document.querySelector("#send-photo-btn").classList.add("disabled");
|
document.querySelector("#send-photo-btn").classList.add("disabled");
|
||||||
document.querySelector("#cancel-photo-btn").classList.add("disabled");
|
document.querySelector("#cancel-photo-btn").classList.add("disabled");
|
||||||
var canvas = document.querySelector("#sb-canvas");
|
var canvas = document.querySelector("#sb-canvas");
|
||||||
var context = canvas.getContext("2d");
|
var context = canvas.getContext("2d");
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
countdown.stop();
|
Countdown.stop();
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function updateSendCountdown() {
|
|
||||||
document.querySelector("#sb-countdown").innerText = "" + countdown.seconds;
|
|
||||||
console.log("deleting photo in " + countdown.seconds + " seconds");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,23 +194,44 @@ function isBlank(canvas) {
|
||||||
|
|
||||||
function sendPhoto() {
|
function sendPhoto() {
|
||||||
console.log("send photo");
|
console.log("send photo");
|
||||||
countdown.stop();
|
Countdown.stop();
|
||||||
document.querySelector("#sb-message").style.visibility = "hidden";
|
|
||||||
var canvas = document.querySelector("#sb-canvas");
|
var canvas = document.querySelector("#sb-canvas");
|
||||||
if (isBlank(canvas)) {
|
if (!canvas || isBlank(canvas)) {
|
||||||
var msg = "I cowardly refuse to send a blank image.";
|
var msg = "I cowardly refuse to send a blank image.";
|
||||||
console.log(msg)
|
console.log(msg);
|
||||||
M.toast({"html": msg});
|
iziToast.warning({
|
||||||
|
"title": msg,
|
||||||
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return sendData(canvas.toDataURL("image/jpeg"));
|
return sendData(canvas.toDataURL("image/jpeg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function _takePhoto() {
|
function _takePhoto(message) {
|
||||||
console.log("take photo");
|
console.log("take photo");
|
||||||
document.querySelector("#sb-message").style.visibility = "visible";
|
|
||||||
var video = document.querySelector("#sb-video");
|
var video = document.querySelector("#sb-video");
|
||||||
|
if (!(video.offsetWidth && video.offsetHeight)) {
|
||||||
|
var msg = "missing video";
|
||||||
|
console.log(msg);
|
||||||
|
iziToast.warning({
|
||||||
|
"title": msg,
|
||||||
|
"message": "please grant camera permissions",
|
||||||
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
var canvas = document.querySelector("#sb-canvas");
|
var canvas = document.querySelector("#sb-canvas");
|
||||||
var context = canvas.getContext("2d");
|
var context = canvas.getContext("2d");
|
||||||
canvas.width = video.offsetWidth;
|
canvas.width = video.offsetWidth;
|
||||||
|
@ -171,12 +256,22 @@ function _takePhoto() {
|
||||||
context.drawImage(video, 0, 0, video.offsetWidth, video.offsetHeight);
|
context.drawImage(video, 0, 0, video.offsetWidth, video.offsetHeight);
|
||||||
document.querySelector("#send-photo-btn").classList.remove("disabled");
|
document.querySelector("#send-photo-btn").classList.remove("disabled");
|
||||||
document.querySelector("#cancel-photo-btn").classList.remove("disabled");
|
document.querySelector("#cancel-photo-btn").classList.remove("disabled");
|
||||||
countdown.start(5, cancelPhoto, updateSendCountdown);
|
iziToast.question({
|
||||||
|
"title": "do you like what you see?",
|
||||||
|
"message": message || "press \"share photo\" to publish!",
|
||||||
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
|
Countdown.start(5, cancelPhoto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function takePhoto() {
|
function takePhoto(msg) {
|
||||||
window.setTimeout(_takePhoto, 2000);
|
window.setTimeout(function() { _takePhoto(msg); }, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -198,7 +293,16 @@ function initCamera() {
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
console.log("unable to open camera");
|
console.log("unable to open camera");
|
||||||
console.log(err);
|
console.log(err);
|
||||||
M.toast({"html": "unable to open camera; please reload this page: " + err});
|
iziToast.error({
|
||||||
|
"title": "unable to open camera",
|
||||||
|
"message": "please reload this page: " + err,
|
||||||
|
"titleSize": "3em",
|
||||||
|
"messageSize": "2em",
|
||||||
|
"close": false,
|
||||||
|
"drag": false,
|
||||||
|
"pauseOnHover": false,
|
||||||
|
"position": "topCenter"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""toot-my-t-shirt
|
"""toot-my-t-shirt
|
||||||
|
|
||||||
Copyright 2018 Davide Alberani <da@mimante.net>
|
Copyright 2018-2019 Davide Alberani <da@mimante.net>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import json
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -25,13 +27,17 @@ import tornado.httpserver
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
from tornado.options import define, options
|
from tornado.options import define, options
|
||||||
import tornado.web
|
import tornado.web
|
||||||
|
import tornado.websocket
|
||||||
from tornado import gen, escape
|
from tornado import gen, escape
|
||||||
|
|
||||||
from mastodon import Mastodon
|
from mastodon import Mastodon
|
||||||
|
|
||||||
|
|
||||||
API_VERSION = '1.0'
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
|
# Keep track of WebSocket connections.
|
||||||
|
_ws_clients = {}
|
||||||
|
|
||||||
|
re_slashes = re.compile(r'//+')
|
||||||
|
|
||||||
class Socialite:
|
class Socialite:
|
||||||
def __init__(self, options, logger=None):
|
def __init__(self, options, logger=None):
|
||||||
|
@ -190,6 +196,85 @@ class PublishHandler(BaseHandler):
|
||||||
self.write(reply)
|
self.write(reply)
|
||||||
|
|
||||||
|
|
||||||
|
class ButtonHandler(BaseHandler):
|
||||||
|
@gen.coroutine
|
||||||
|
def post(self, **kwargs):
|
||||||
|
reply = {'success': True}
|
||||||
|
self.send_ws_message('/ws', json.dumps({"source": "button", "action": "clicked"}))
|
||||||
|
self.write(reply)
|
||||||
|
|
||||||
|
@gen.coroutine
|
||||||
|
def send_ws_message(self, path, message):
|
||||||
|
"""Send a WebSocket message to all the connected clients.
|
||||||
|
|
||||||
|
:param path: partial path used to build the WebSocket url
|
||||||
|
:type path: str
|
||||||
|
:param message: message to send
|
||||||
|
:type message: str
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
url = '%s://localhost:%s/ws?uuid=bigredbutton' % ('wss' if self.ssl_options else 'ws',
|
||||||
|
self.global_options.port)
|
||||||
|
self.logger.info(url)
|
||||||
|
req = tornado.httpclient.HTTPRequest(url, validate_cert=False)
|
||||||
|
ws = yield tornado.websocket.websocket_connect(req)
|
||||||
|
ws.write_message(message)
|
||||||
|
ws.close()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error('Error yielding WebSocket message: %s', e)
|
||||||
|
|
||||||
|
|
||||||
|
class WSHandler(tornado.websocket.WebSocketHandler):
|
||||||
|
def initialize(self, **kwargs):
|
||||||
|
"""Add every passed (key, value) as attributes of the instance."""
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def _clean_url(self, url):
|
||||||
|
url = re_slashes.sub('/', url)
|
||||||
|
ridx = url.rfind('?')
|
||||||
|
if ridx != -1:
|
||||||
|
url = url[:ridx]
|
||||||
|
return url
|
||||||
|
|
||||||
|
def open(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
self.uuid = self.get_argument('uuid')
|
||||||
|
except:
|
||||||
|
self.uuid = None
|
||||||
|
url = self._clean_url(self.request.uri)
|
||||||
|
_ws_clients.setdefault(url, {})
|
||||||
|
if self.uuid and self.uuid not in _ws_clients[url]:
|
||||||
|
_ws_clients[url][self.uuid] = self
|
||||||
|
self.logger.debug('WSHandler.open %s clients connected' % len(_ws_clients[url]))
|
||||||
|
|
||||||
|
def on_message(self, message):
|
||||||
|
url = self._clean_url(self.request.uri)
|
||||||
|
self.logger.debug('WSHandler.on_message url: %s' % url)
|
||||||
|
count = 0
|
||||||
|
_to_delete = set()
|
||||||
|
current_uuid = None
|
||||||
|
try:
|
||||||
|
current_uuid = self.get_argument('uuid')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
for uuid, client in _ws_clients.get(url, {}).items():
|
||||||
|
if uuid and uuid == current_uuid:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
client.write_message(message)
|
||||||
|
except:
|
||||||
|
_to_delete.add(uuid)
|
||||||
|
continue
|
||||||
|
count += 1
|
||||||
|
for uuid in _to_delete:
|
||||||
|
try:
|
||||||
|
del _ws_clients[url][uuid]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self.logger.debug('WSHandler.on_message sent message to %d clients' % count)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
"""Run the Tornado web application."""
|
"""Run the Tornado web application."""
|
||||||
# command line arguments; can also be written in a configuration file,
|
# command line arguments; can also be written in a configuration file,
|
||||||
|
@ -226,12 +311,16 @@ def run():
|
||||||
ssl_options = dict(certfile=options.ssl_cert, keyfile=options.ssl_key)
|
ssl_options = dict(certfile=options.ssl_cert, keyfile=options.ssl_key)
|
||||||
|
|
||||||
socialite = Socialite(options, logger=logger)
|
socialite = Socialite(options, logger=logger)
|
||||||
init_params = dict(global_options=options, socialite=socialite)
|
init_params = dict(global_options=options, ssl_options=ssl_options, socialite=socialite, logger=logger)
|
||||||
|
|
||||||
_publish_path = r"/publish/?"
|
_publish_path = r"/publish/?"
|
||||||
|
_button_path = r"/button/?"
|
||||||
application = tornado.web.Application([
|
application = tornado.web.Application([
|
||||||
|
(r'/ws', WSHandler, init_params),
|
||||||
(_publish_path, PublishHandler, init_params),
|
(_publish_path, PublishHandler, init_params),
|
||||||
(r'/v%s%s' % (API_VERSION, _publish_path), PublishHandler, init_params),
|
(r'/v%s%s' % (API_VERSION, _publish_path), PublishHandler, init_params),
|
||||||
|
(_button_path, ButtonHandler, init_params),
|
||||||
|
(r'/v%s%s' % (API_VERSION, _button_path), ButtonHandler, init_params),
|
||||||
(r"/(?:index.html)?", RootHandler, init_params),
|
(r"/(?:index.html)?", RootHandler, init_params),
|
||||||
(r'/?(.*)', tornado.web.StaticFileHandler, {"path": "static"})
|
(r'/?(.*)', tornado.web.StaticFileHandler, {"path": "static"})
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue