feat(App, RealityOverridePage): add Reality Override page and integrate WebSocket message broadcasting
This commit is contained in:
parent
ab5d9bb486
commit
0df2b9cae5
|
|
@ -10,6 +10,7 @@ import OtherPage from './components/OtherPage';
|
||||||
import ToolsPage from './components/ToolsPage';
|
import ToolsPage from './components/ToolsPage';
|
||||||
import SearchOverlay from './components/SearchOverlay';
|
import SearchOverlay from './components/SearchOverlay';
|
||||||
import MediaManager from './components/MediaManager';
|
import MediaManager from './components/MediaManager';
|
||||||
|
import RealityOverridePage from './components/RealityOverridePage';
|
||||||
import logoSvg from '../imports/logo.svg';
|
import logoSvg from '../imports/logo.svg';
|
||||||
import { useSettings } from './settings';
|
import { useSettings } from './settings';
|
||||||
|
|
||||||
|
|
@ -35,7 +36,8 @@ type AppId =
|
||||||
| 'charset-editor'
|
| 'charset-editor'
|
||||||
| 'petscii-editor'
|
| 'petscii-editor'
|
||||||
| 'idle-animation'
|
| 'idle-animation'
|
||||||
| 'loading-animation';
|
| 'loading-animation'
|
||||||
|
| 'reality-override';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [currentPage, setCurrentPage] = useState<Page>('status');
|
const [currentPage, setCurrentPage] = useState<Page>('status');
|
||||||
|
|
@ -105,6 +107,7 @@ export default function App() {
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
<AppCard icon={<Activity className="w-7 h-7" />} label="Idle Animation" onClick={() => setCurrentPage('idle-animation')} />
|
<AppCard icon={<Activity className="w-7 h-7" />} label="Idle Animation" onClick={() => setCurrentPage('idle-animation')} />
|
||||||
<AppCard icon={<Loader2 className="w-7 h-7" />} label="Loading Animation" onClick={() => setCurrentPage('loading-animation')} />
|
<AppCard icon={<Loader2 className="w-7 h-7" />} label="Loading Animation" onClick={() => setCurrentPage('loading-animation')} />
|
||||||
|
<AppCard icon={<Wifi className="w-7 h-7" />} label="Reality Override" onClick={() => setCurrentPage('reality-override')} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -141,7 +144,8 @@ export default function App() {
|
||||||
'charset-editor': <AppPage title="Character Set Editor" onBack={() => setCurrentPage('apps')} />,
|
'charset-editor': <AppPage title="Character Set Editor" onBack={() => setCurrentPage('apps')} />,
|
||||||
'petscii-editor': <AppPage title="Petscii Editor" onBack={() => setCurrentPage('apps')} />,
|
'petscii-editor': <AppPage title="Petscii Editor" onBack={() => setCurrentPage('apps')} />,
|
||||||
'idle-animation': <AppPage title="Idle Animation" onBack={() => setCurrentPage('apps')} />,
|
'idle-animation': <AppPage title="Idle Animation" onBack={() => setCurrentPage('apps')} />,
|
||||||
'loading-animation': <AppPage title="Loading Animation" onBack={() => setCurrentPage('apps')} />
|
'loading-animation': <AppPage title="Loading Animation" onBack={() => setCurrentPage('apps')} />,
|
||||||
|
'reality-override': <RealityOverridePage onBack={() => setCurrentPage('apps')} />
|
||||||
};
|
};
|
||||||
|
|
||||||
// AppCard component for app grid
|
// AppCard component for app grid
|
||||||
|
|
|
||||||
296
src/app/components/RealityOverridePage.tsx
Normal file
296
src/app/components/RealityOverridePage.tsx
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { ChevronLeft, Loader2, Wifi, WifiOff } from 'lucide-react';
|
||||||
|
|
||||||
|
// ── Rocket sprite (6w × 10h, nose pointing UP) ───────────────────────────────
|
||||||
|
// Type: 0=hull, 1=trim, 2=porthole
|
||||||
|
const HULL: [number, number, 0 | 1 | 2][] = [
|
||||||
|
[2,0,0],[3,0,0],
|
||||||
|
[1,1,0],[2,1,0],[3,1,0],[4,1,0],
|
||||||
|
[0,2,0],[1,2,0],[2,2,2],[3,2,2],[4,2,0],[5,2,0],
|
||||||
|
[0,3,0],[1,3,0],[2,3,0],[3,3,0],[4,3,0],[5,3,0],
|
||||||
|
[0,4,0],[1,4,0],[2,4,0],[3,4,0],[4,4,0],[5,4,0],
|
||||||
|
[1,5,0],[2,5,0],[3,5,0],[4,5,0],
|
||||||
|
[1,6,1],[2,6,0],[3,6,0],[4,6,1],
|
||||||
|
];
|
||||||
|
// Alternating flame patterns
|
||||||
|
const FLAMES: [number, number][][] = [
|
||||||
|
[[2,7],[3,7],[1,8],[2,8],[3,8],[4,8],[2,9],[3,9]],
|
||||||
|
[[2,7],[3,7],[2,8],[3,8],[1,9],[4,9]],
|
||||||
|
];
|
||||||
|
|
||||||
|
const S = 2; // sprite cell size in CSS pixels
|
||||||
|
|
||||||
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||||
|
interface Star { x:number; y:number; sz:number; ph:number; spd:number; col:[number,number,number]; }
|
||||||
|
interface Rocket { x:number; y:number; vx:number; vy:number; rot:number; color:string; ff:number; tick:number; }
|
||||||
|
interface Msg { text:string; id:number; }
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
const STAR_PALETTE: [number,number,number][] = [
|
||||||
|
[255,255,255],[255,240,210],[210,220,255],[255,210,210],[200,255,230],
|
||||||
|
];
|
||||||
|
const ROCKET_COLORS = ['#C0C0C0','#FF9090','#88AAFF','#AAFFAA','#FFDDAA','#FFAAFF'];
|
||||||
|
|
||||||
|
function mkStar(W: number, H: number): Star {
|
||||||
|
return {
|
||||||
|
x: Math.random()*W|0, y: Math.random()*H|0,
|
||||||
|
sz: Math.random()<0.8 ? 1 : Math.random()<0.6 ? 2 : 3,
|
||||||
|
ph: Math.random()*Math.PI*2,
|
||||||
|
spd: Math.random()*0.04+0.005,
|
||||||
|
col: STAR_PALETTE[Math.random()*STAR_PALETTE.length|0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mkRocket(W: number, H: number, initialX?: number): Rocket {
|
||||||
|
const dir = Math.random()<0.5 ? 1 : -1;
|
||||||
|
const speed = Math.random()*1.5+0.8;
|
||||||
|
return {
|
||||||
|
x: initialX ?? (dir>0 ? -20 : W+20),
|
||||||
|
y: Math.random()*H*0.75+H*0.1,
|
||||||
|
vx: dir*speed,
|
||||||
|
vy: (Math.random()-0.5)*0.5,
|
||||||
|
rot: dir>0 ? Math.PI/2 : -Math.PI/2,
|
||||||
|
color: ROCKET_COLORS[Math.random()*ROCKET_COLORS.length|0],
|
||||||
|
ff: 0, tick: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildNebula(W: number, H: number): HTMLCanvasElement {
|
||||||
|
const c = document.createElement('canvas');
|
||||||
|
c.width = W; c.height = H;
|
||||||
|
const ctx = c.getContext('2d')!;
|
||||||
|
const blobs = [
|
||||||
|
{ cx:W*.25, cy:H*.35, sx:W*.2, sy:H*.2, r:80, g:0, b:170 },
|
||||||
|
{ cx:W*.72, cy:H*.22, sx:W*.16, sy:H*.2, r:0, g:40, b:155 },
|
||||||
|
{ cx:W*.55, cy:H*.65, sx:W*.22, sy:H*.2, r:65, g:0, b:115 },
|
||||||
|
{ cx:W*.12, cy:H*.75, sx:W*.14, sy:H*.15, r:0, g:55, b:90 },
|
||||||
|
{ cx:W*.88, cy:H*.6, sx:W*.13, sy:H*.15, r:90, g:15, b:70 },
|
||||||
|
];
|
||||||
|
for (const { cx,cy,sx,sy,r,g,b } of blobs) {
|
||||||
|
for (let i = 0; i < 600; i++) {
|
||||||
|
const u = Math.random()+1e-9, v = Math.random();
|
||||||
|
const z1 = Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v);
|
||||||
|
const z2 = Math.sqrt(-2*Math.log(u))*Math.sin(2*Math.PI*v);
|
||||||
|
const a = Math.min((0.04+Math.random()*0.12)/(1+(z1*z1+z2*z2)*0.12), 0.18);
|
||||||
|
if (a < 0.01) continue;
|
||||||
|
const sz = (Math.random()*4+2)|0;
|
||||||
|
ctx.fillStyle = `rgba(${r},${g},${b},${a})`;
|
||||||
|
ctx.fillRect((cx+z1*sx*.5)|0, (cy+z2*sy*.5)|0, sz, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pivot is sprite center (3*S, 5*S). rotate(±π/2) to aim left/right.
|
||||||
|
function drawRocket(ctx: CanvasRenderingContext2D, rk: Rocket) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(rk.x, rk.y);
|
||||||
|
ctx.rotate(rk.rot);
|
||||||
|
ctx.translate(-3*S, -5*S);
|
||||||
|
for (const [dc, dr, t] of HULL) {
|
||||||
|
ctx.fillStyle = t===2 ? '#44DDFF' : t===1 ? '#555' : rk.color;
|
||||||
|
ctx.fillRect(dc*S, dr*S, S, S);
|
||||||
|
}
|
||||||
|
const flames = FLAMES[rk.ff%2];
|
||||||
|
for (let i = 0; i < flames.length; i++) {
|
||||||
|
ctx.fillStyle = i%2===0 ? '#FF6600' : '#FFCC00';
|
||||||
|
ctx.fillRect(flames[i][0]*S, flames[i][1]*S, S, S);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Component ─────────────────────────────────────────────────────────────────
|
||||||
|
export default function RealityOverridePage({ onBack }: { onBack: () => void }) {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const animRef = useRef<number>(0);
|
||||||
|
const starsRef = useRef<Star[]>([]);
|
||||||
|
const rocketsRef = useRef<Rocket[]>([]);
|
||||||
|
const nebulaRef = useRef<HTMLCanvasElement | null>(null);
|
||||||
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
|
const msgIdRef = useRef(0);
|
||||||
|
|
||||||
|
const [wsStatus, setWsStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting');
|
||||||
|
const [currentCmd, setCurrentCmd] = useState<Msg | null>(null);
|
||||||
|
const [history, setHistory] = useState<Msg[]>([]);
|
||||||
|
const fadeTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||||
|
|
||||||
|
// ── Canvas animation loop ───────────────────────────────────────────────────
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
let prevW = 0, prevH = 0;
|
||||||
|
|
||||||
|
const loop = () => {
|
||||||
|
const W = canvas.clientWidth, H = canvas.clientHeight;
|
||||||
|
if (!W || !H) { animRef.current = requestAnimationFrame(loop); return; }
|
||||||
|
|
||||||
|
if (W !== prevW || H !== prevH) {
|
||||||
|
canvas.width = W; canvas.height = H;
|
||||||
|
prevW = W; prevH = H;
|
||||||
|
nebulaRef.current = buildNebula(W, H);
|
||||||
|
starsRef.current = Array.from({ length: 200 }, () => mkStar(W, H));
|
||||||
|
// Spread initial rockets across the screen
|
||||||
|
rocketsRef.current = [0,1,2].map(i => mkRocket(W, H, (i+0.5)*W/3));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
ctx.fillStyle = '#000008';
|
||||||
|
ctx.fillRect(0, 0, W, H);
|
||||||
|
|
||||||
|
// Nebula
|
||||||
|
if (nebulaRef.current) ctx.drawImage(nebulaRef.current, 0, 0);
|
||||||
|
|
||||||
|
// Stars
|
||||||
|
for (const st of starsRef.current) {
|
||||||
|
st.ph += st.spd;
|
||||||
|
const br = Math.sin(st.ph)*0.4+0.6;
|
||||||
|
const [r,g,b] = st.col;
|
||||||
|
ctx.fillStyle = `rgba(${r},${g},${b},${br})`;
|
||||||
|
ctx.fillRect(st.x, st.y, st.sz, st.sz);
|
||||||
|
// Sparkle cross on bright large stars
|
||||||
|
if (st.sz >= 3 && br > 0.85) {
|
||||||
|
ctx.fillStyle = `rgba(${r},${g},${b},${br*0.35})`;
|
||||||
|
ctx.fillRect(st.x-2, st.y+1, st.sz+4, 1);
|
||||||
|
ctx.fillRect(st.x+1, st.y-2, 1, st.sz+4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rockets
|
||||||
|
const PAD = 60;
|
||||||
|
for (let i = rocketsRef.current.length-1; i >= 0; i--) {
|
||||||
|
const rk = rocketsRef.current[i];
|
||||||
|
rk.x += rk.vx; rk.y += rk.vy;
|
||||||
|
rk.tick++;
|
||||||
|
if (rk.tick >= 8) { rk.tick = 0; rk.ff++; }
|
||||||
|
drawRocket(ctx, rk);
|
||||||
|
if (rk.x < -PAD || rk.x > W+PAD || rk.y < -PAD || rk.y > H+PAD) {
|
||||||
|
rocketsRef.current.splice(i, 1);
|
||||||
|
setTimeout(() => {
|
||||||
|
const cv = canvasRef.current;
|
||||||
|
if (cv) rocketsRef.current.push(mkRocket(cv.clientWidth, cv.clientHeight));
|
||||||
|
}, Math.random()*4000+500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animRef.current = requestAnimationFrame(loop);
|
||||||
|
};
|
||||||
|
|
||||||
|
animRef.current = requestAnimationFrame(loop);
|
||||||
|
return () => cancelAnimationFrame(animRef.current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ── WebSocket ───────────────────────────────────────────────────────────────
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
let ws: WebSocket | null = null;
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
if (cancelled) return;
|
||||||
|
ws = new WebSocket(`ws://${window.location.hostname}/ws`);
|
||||||
|
wsRef.current = ws;
|
||||||
|
setWsStatus('connecting');
|
||||||
|
ws.onopen = () => { if (!cancelled) setWsStatus('connected'); };
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
if (cancelled) return;
|
||||||
|
const id = msgIdRef.current++;
|
||||||
|
const msg: Msg = { text: String(e.data), id };
|
||||||
|
setCurrentCmd(msg);
|
||||||
|
setHistory(prev => [...prev.slice(-9), msg]);
|
||||||
|
clearTimeout(fadeTimer.current);
|
||||||
|
fadeTimer.current = setTimeout(() => setCurrentCmd(null), 5000);
|
||||||
|
};
|
||||||
|
ws.onclose = () => {
|
||||||
|
if (cancelled) return;
|
||||||
|
setWsStatus('disconnected');
|
||||||
|
setTimeout(connect, 3000);
|
||||||
|
};
|
||||||
|
ws.onerror = () => ws?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
connect();
|
||||||
|
return () => { cancelled = true; ws?.close(); };
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-full bg-black overflow-hidden">
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className="absolute inset-0 w-full h-full"
|
||||||
|
style={{ imageRendering: 'pixelated' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Back */}
|
||||||
|
<button
|
||||||
|
onClick={onBack}
|
||||||
|
className="absolute top-3 left-3 z-10 flex items-center gap-1 text-white/50 hover:text-white text-xs px-2 py-1 rounded bg-black/40 border border-white/10"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-3.5 h-3.5" /> Back
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* WS status */}
|
||||||
|
<div className="absolute top-3 right-3 z-10 flex items-center gap-1.5 text-xs px-2 py-1 rounded bg-black/40 border border-white/10">
|
||||||
|
{wsStatus === 'connecting' && <><Loader2 className="w-3.5 h-3.5 text-yellow-400 animate-spin" /><span className="text-yellow-400">Connecting…</span></>}
|
||||||
|
{wsStatus === 'connected' && <><Wifi className="w-3.5 h-3.5 text-green-400" /><span className="text-green-400">Connected</span></>}
|
||||||
|
{wsStatus === 'disconnected' && <><WifiOff className="w-3.5 h-3.5 text-red-400" /><span className="text-red-400">Reconnecting…</span></>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CSS keyframe for command flash */}
|
||||||
|
<style>{`
|
||||||
|
@keyframes cmd-flash {
|
||||||
|
0% { opacity: 0; transform: translate(-50%,-50%) scale(0.92); }
|
||||||
|
10% { opacity: 1; transform: translate(-50%,-50%) scale(1); }
|
||||||
|
70% { opacity: 1; transform: translate(-50%,-50%) scale(1); }
|
||||||
|
100% { opacity: 0; transform: translate(-50%,-50%) scale(1); }
|
||||||
|
}
|
||||||
|
.cmd-flash { animation: cmd-flash 5s ease-out forwards; }
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
{/* Idle title — shown only before first message */}
|
||||||
|
{history.length === 0 && (
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center z-10 pointer-events-none select-none">
|
||||||
|
<div
|
||||||
|
className="text-white/20 text-xl font-mono tracking-[0.3em] uppercase"
|
||||||
|
style={{ textShadow: '0 0 30px rgba(120,60,255,0.7)' }}
|
||||||
|
>
|
||||||
|
Reality Override
|
||||||
|
</div>
|
||||||
|
<div className="text-white/10 text-xs font-mono tracking-widest mt-2 uppercase">
|
||||||
|
Awaiting Commands<span className="animate-pulse">_</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Centered current command */}
|
||||||
|
{currentCmd && (
|
||||||
|
<div
|
||||||
|
key={currentCmd.id}
|
||||||
|
className="cmd-flash absolute z-10 pointer-events-none select-none text-center"
|
||||||
|
style={{ top: '50%', left: '50%', transform: 'translate(-50%,-50%)', maxWidth: '80%' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="font-mono text-2xl font-bold tracking-wide text-white break-words"
|
||||||
|
style={{ textShadow: '0 0 24px rgba(80,200,255,0.9), 0 0 60px rgba(80,200,255,0.4)' }}
|
||||||
|
>
|
||||||
|
{currentCmd.text}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* History log */}
|
||||||
|
{history.length > 0 && (
|
||||||
|
<div className="absolute bottom-4 left-4 right-4 z-10">
|
||||||
|
<div className="bg-black/70 border border-green-900/40 rounded px-3 py-2 space-y-0.5">
|
||||||
|
{history.map(m => (
|
||||||
|
<div key={m.id} className="text-green-400 text-xs font-mono truncate">
|
||||||
|
<span className="text-green-800 mr-2">></span>{m.text}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
webdav3.py
27
webdav3.py
|
|
@ -43,7 +43,10 @@ from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
from socketserver import ThreadingMixIn
|
from socketserver import ThreadingMixIn
|
||||||
import urllib.request, urllib.parse, urllib.error, urllib.parse
|
import urllib.request, urllib.parse, urllib.error, urllib.parse
|
||||||
from time import timezone, strftime, localtime, gmtime
|
from time import timezone, strftime, localtime, gmtime
|
||||||
import os, sys, re, shutil, struct, uuid, hashlib, mimetypes, base64, socket
|
import os, sys, re, shutil, struct, threading, uuid, hashlib, mimetypes, base64, socket
|
||||||
|
|
||||||
|
_ws_lock = threading.Lock()
|
||||||
|
_ws_clients: set = set() # connected WebSocket sockets
|
||||||
|
|
||||||
# Debug message ( True / False )
|
# Debug message ( True / False )
|
||||||
sys_debug = False
|
sys_debug = False
|
||||||
|
|
@ -697,9 +700,18 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def ws_process_message(self, message: str) -> str:
|
def ws_process_message(self, message: str) -> str:
|
||||||
"""Override this to handle incoming WebSocket messages. Return the response string."""
|
"""Override this to handle incoming WebSocket messages. Return the response to broadcast."""
|
||||||
return message # echo by default
|
return message # echo by default
|
||||||
|
|
||||||
|
def _ws_broadcast(self, payload: bytes):
|
||||||
|
with _ws_lock:
|
||||||
|
clients = set(_ws_clients)
|
||||||
|
for sock in clients:
|
||||||
|
try:
|
||||||
|
self._ws_send(sock, payload)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def _ws_send(self, sock, payload: bytes):
|
def _ws_send(self, sock, payload: bytes):
|
||||||
length = len(payload)
|
length = len(payload)
|
||||||
if length < 126:
|
if length < 126:
|
||||||
|
|
@ -721,9 +733,11 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
||||||
self.send_header('Sec-WebSocket-Accept', accept)
|
self.send_header('Sec-WebSocket-Accept', accept)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
client = f'{self.client_address[0]}:{self.client_address[1]}'
|
client = f'{self.client_address[0]}:{self.client_address[1]}'
|
||||||
print(f'[WS] {client} connected')
|
|
||||||
sock = self.connection
|
sock = self.connection
|
||||||
sock.settimeout(None)
|
sock.settimeout(None)
|
||||||
|
with _ws_lock:
|
||||||
|
_ws_clients.add(sock)
|
||||||
|
print(f'[WS] {client} connected (total: {len(_ws_clients)})')
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
b0, b1 = self._ws_recv(sock, 2)
|
b0, b1 = self._ws_recv(sock, 2)
|
||||||
|
|
@ -749,10 +763,13 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
||||||
print(f'[WS] {client} recv ({length}b): {message}')
|
print(f'[WS] {client} recv ({length}b): {message}')
|
||||||
response = self.ws_process_message(message)
|
response = self.ws_process_message(message)
|
||||||
if response is not None:
|
if response is not None:
|
||||||
self._ws_send(sock, response.encode('utf-8'))
|
self._ws_broadcast(response.encode('utf-8'))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
print(f'[WS] {client} disconnected')
|
finally:
|
||||||
|
with _ws_lock:
|
||||||
|
_ws_clients.discard(sock)
|
||||||
|
print(f'[WS] {client} disconnected (total: {len(_ws_clients)})')
|
||||||
|
|
||||||
def do_GET(self, onlyhead=False):
|
def do_GET(self, onlyhead=False):
|
||||||
if (self.path == '/ws'
|
if (self.path == '/ws'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user