aardio 文档
aardio 范例: qrfs - 扫码快传 v2.3
//扫码传文件
import fonts.fontAwesome;
import win.ui;
/*DSG{{*/
var winform = win.form(text="qrfs - 扫码快传 v2.3";right=807;bottom=465;bgcolor=0xFFFFFF;border="none";max=false)
winform.add(
bk={cls="bk";left=-2;top=-5;right=810;bottom=29;bgcolor=15790320;dl=1;dr=1;dt=1;forecolor=12639424;linearGradient=0;z=10};
bkplus={cls="bkplus";text="qrfs - 扫码快传 v2.3";left=35;top=3;right=220;bottom=25;align="left";color=5921370;dl=1;dt=1;z=11};
btnOpen={cls="plus";text='\uF115';left=444;top=50;right=479;bottom=75;dr=1;dt=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=6};
btnOpenUpload={cls="plus";text="打开上传目录";left=161;top=86;right=302;bottom=115;dl=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={left=8;top=2}};iconText='\uF115';notify=1;textPadding={left=20};z=12};
btnStart={cls="plus";text="重启服务";left=655;top=47;right=755;bottom=76;bgcolor=14935259;dr=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={left=8;top=2}};iconText='\uF233';notify=1;textPadding={left=20};z=5};
editDocumentRoot={cls="plus";left=131;top=49;right=430;bottom=73;align="right";border={bottom=1;color=-8355712};dl=1;dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=8};
editPassword={cls="plus";left=441;top=84;right=632;bottom=108;align="left";border={bottom=1;color=-8355712};dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=14};
editPort={cls="plus";left=550;top=49;right=628;bottom=73;align="left";border={bottom=1;color=-8355712};dr=1;dt=1;editable=1;font=LOGFONT(h=-16);z=9};
plus={cls="plus";text="访问密码:";left=332;top=90;right=433;bottom=114;align="right";dl=1;dr=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=13};
plus2={cls="plus";left=38;top=128;right=472;bottom=422;align="left";border={color=-16744448;radius=8;width=1};db=1;dl=1;dr=1;dt=1;font=LOGFONT(h=-14);textPadding={left=16};valign="top";z=1};
qr={cls="plus";left=499;top=132;right=760;bottom=418;db=1;dr=1;dt=1;repeat="scale";z=7};
radioQrClipboard={cls="radiobutton";text="共享剪贴板";left=685;top=435;right=772;bottom=454;bgcolor=0xFFFFFF;db=1;dr=1;z=18};
radioQrRootDir={cls="radiobutton";text="根目录";left=502;top=435;right=589;bottom=454;bgcolor=0xFFFFFF;db=1;dr=1;z=16};
radioQrUploadDir={cls="radiobutton";text="上传目录";left=593;top=435;right=680;bottom=454;bgcolor=0xFFFFFF;db=1;dr=1;z=17};
static={cls="plus";text="端口:";left=484;top=52;right=547;bottom=76;align="right";dr=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=3};
static2={cls="plus";text="网站根目录:";left=15;top=52;right=129;bottom=76;align="right";dl=1;dt=1;font=LOGFONT(h=-15);transparent=1;z=4};
syslink={cls="syslink";text='<a href="https://github.com/aardio/qrfs">开源项目</a>';left=43;top=437;right=176;bottom=457;bgcolor=0xFFFFFF;db=1;dl=1;z=15};
txtMessage={cls="richedit";left=42;top=132;right=469;bottom=418;autohscroll=false;db=1;dl=1;dr=1;dt=1;link=1;multiline=1;vscroll=1;z=2}
)
/*}}*/
import fsys.update.simpleMain;
if( fsys.update.simpleMain(
"qrfs - 扫码快传",
"http://d.aardio.com/qrfs/update/",
io.appData("/aardio/std/qrfs/app/update"),
function(version,description,status){})){
return 0;
}
import fsys.config;
config = fsys.config(io.appData("aardio/std/qrfs"));
import sessionHandler.default;
sessionHandler.default.root = io.appData("aardio/std/qrfs/session")
if( io.exist(config.winform.txtMessage) ){
winform.txtMessage.text = config.winform.txtMessage;
}
else {
winform.txtMessage.text = io.getSpecial(0x5/*_CSIDL_MYDOCUMENTS*/)
}
if(config.winform.qrDir=="upload"){
winform.radioQrUploadDir.checked = true;
}
elseif(config.winform.qrDir=="clipboard"){
winform.radioQrClipboard.checked = true;
}
else {
winform.radioQrRootDir.checked = true;
}
import web.socket.server;
var wsrv = web.socket.server();
var srvHttp = wsrv.httpServer;
/*
wsrv.httpServer 是实现单线程异步 HTTP 服务端的 wsock.tcp.asynHttpServer 对象。
浏览器组件发起异步 HTTP 请求支持 wsock.tcp.asynHttpServer。请不要用 inet.http 等
阻塞请求同一线程创建的 asynHttpServer,这会导致 asynHttpServer 没有机会响应请求而导致死锁,
如果确有这样的需求,可创建线程发起请求,或改用基于多线程的 wsock.tcp.simpleHttpServer。
*/
srvHttp.documentRoot = winform.txtMessage.text;
srvHttp.userToken = string.random(18);
winform.editPassword.text = srvHttp.userToken;
import fsys.info;
var cacheSysIcons = {}
var getSysIconIndex = function(path){
var sfi;
sfi = fsys.info.get(path, 0x100/*_SHGFI_ICON*/ | 0x4000/*_SHGFI_SYSICONINDEX*/|0x10/*_SHGFI_USEFILEATTRIBUTES*/,
..fsys.isDir(path)?0x10/*_FILE_ATTRIBUTE_DIRECTORY*/?0x80/*_FILE_ATTRIBUTE_NORMAL*/);
if( !(sfi.returnValue ) ) {
return;
}
if(!cacheSysIcons[sfi.iIcon]){
var dataUrl;
var bmp = ..gdip.bitmap(sfi.hIcon,1/*_IMAGE_ICON*/);
if(bmp){
cacheSysIcons[sfi.iIcon] = bmp.saveToBuffer(".png");
bmp.dispose();
}
}
if(sfi.hIcon)::DestroyIcon(sfi.hIcon);
return sfi.iIcon;
}
import win.clip;
var cacheClientIps = {}
srvHttp.run(
function(response,request,session){
//添加禁止缓存的HTTP头
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
response.headers["Pragma"] = "no-cache";
response.headers["Expires"] = "0";
var token = request.get["t"] : session["token"];
if( #srvHttp.userToken && (token != srvHttp.userToken) ){
winform.txtMessage.printf("客户端:%s 连接被拒绝",request.remoteAddr);
response.errorStatus(401)
return;
}
session["token"] = token;
if(!cacheClientIps[request.remoteAddr]){
winform.txtMessage.printf("客户端:%s 已连接",request.remoteAddr);
cacheClientIps[request.remoteAddr] = true;
}
response.headers["Access-Control-Allow-Origin"] = "*";
response.headers["Access-Control-Allow-Headers"] = "*"
if(request.path=="/main.aardio" && request.get["icon"]){
var iconIdx = tonumber(request.get["icon"]);
if(iconIdx!==null){
if(cacheSysIcons[iconIdx]){
response.contentType = "image/png";
response.write(cacheSysIcons[iconIdx])
return;
}
}
response.errorStatus(404);
return;
}
if(request.path=="/upload/main.aardio"){
if(request.method=="DELETE"){
var path = request.postData();
if(path && string.startsWith(path,"/upload/")){
path = ..io.joinpath(srvHttp.documentRoot,path)
if(io.exist(path)){
io.remove(path);
winform.txtMessage.print("已删除:" + path);
response.close();
return;
}
}
response.errorStatus(404);
return;
}
fileData = request.postFileData()
if(fileData){
io.createDir(..io.joinpath(srvHttp.documentRoot,"upload"))
winform.txtMessage.print(..io.joinpath(srvHttp.documentRoot,"upload"))
var fileName = ..io.joinpath(srvHttp.documentRoot,"upload",fileData.filepond.filename)
var ok,err = fileData.filepond.save(fileName);
if(!ok){ response.error(err); }
winform.txtMessage.text = 'HTTP 服务端已启动: \n';
winform.txtMessage.print( srvHttp.getUrl(,true) + "/?t=" + srvHttp.userToken );
winform.txtMessage.print( "" );
winform.txtMessage.print( "上传成功:" + fileName );
response.contentType = "text/plain";
response.write("/upload/",fileData.filepond.filename)
return response.close()
}
}
if(!fsys.isDir(request.path) && request.path!="/<clipboard>" ) {
if( ..io.exist(request.path)
&& (!_STUDIO_INVOKED || request.path!="/main.aardio") )
return response.loadcode(request.path)
else {
request.path = fsys.getParentDir(request.path)
}
}
response.write(`
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>qrfs - 扫码快传 v2.3</title>
<link href="https://lib.baomitu.com/filepond/4.28.2/filepond.min.css" rel="stylesheet">
<script src="https://lib.baomitu.com/filepond/4.28.2/filepond.min.js"></script>
<script>
</script>
<style>
html{
margin: 0;
padding: 0;
}
body {
padding-top: 60px;
height: 100vh;
display: flex;
flex-direction: column;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
li{ list-style-type:none; }
.filepond-container {
position: fixed;
top: 0;
right: 0;
width: 100%;
height: min-content;
z-index: 1000;
}
.filepond--root {
min-height: 30px !important;
margin-bottom: 0;
}
.filepond--drop-label {
height: 60px !important;
}
.filepond--panel-root {
min-height: 30px !important;
border: 2px dashed #99aab5;
}
.filepond--drop-label {
color: #718096;
min-height: 30px !important;
}
.filepond--item {
min-height: 30px !important;
margin: 0 !important;
}
.filepond--file{
min-width: 100%;
padding-right: 80px;
}
.filepond--file-info {
min-height: auto !important;
margin: 0 !important;
}
.filepond--label-action {
color: #3498db;
}
.filepond--file-status {
margin: 0 !important;
min-height: auto !important;
}
.filepond--credits {
display: none;
}
.filepond--file-wrapper {
height: 100% !important;
display: flex;
align-items: center;
}
.filepond--processing-complete-indicator {
margin: 0 !important;
}
ul{
margin-top:0px;
}
p, li {
line-height: 1.7;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 1.1em;
}
li img{
vertical-align: middle;
width: 1.7em !important;
height: 1.7em !important;
padding-right: 5px;
}
a {
color: #0366d6;
text-decoration: none;
font-size: 1.1em !important;
}
a:hover {
text-decoration: underline;
color: #0056b3;
}
a.absent {
color: #cc0000;
}
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0;
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.5em;
margin: 15px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative;
}
h2 {
font-size: 1.3em;
padding-bottom: 6px;
border-bottom: 1px solid #DEE3E8;
color: #333;
margin-top:5px;
margin-bottom: 5px;
vertical-align: middle;
display: flex;
align-items: center;
justify-content: space-between;
}
.back-btn {
padding: 8px 15px;
cursor: pointer;
font-size: 0.8em;
margin-left: 10px;
font-weight: 400;
}
.clipboard-container {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 10px;
max-height: calc(100vh - 20px);
}
.text-container {
flex: 1;
display: flex;
flex-direction: column;
margin-bottom: 15px;
position: relative;
max-height: 100%;
}
.clipboard-content {
width: 100%;
height: 100%;
padding: 15px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 1.1em;
background-color: #fff;
resize: none;
box-sizing: border-box;
line-height: 1.5;
}
.clipboard-content:focus {
outline: none;
border-color: #2196F3;
height: 100% !important;
position: relative !important;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
}
.button-group {
display: flex;
gap: 12px;
margin-bottom: 16px;
position: sticky;
bottom: 0;
background-color: #fff;
padding: 10px 0;
z-index: 100;
}
button {
flex: 1;
padding: 16px 0;
border: none;
border-radius: 8px;
font-size: 1.2em;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
color: white;
}
.copy-btn {
background-color: #2196F3;
}
button:active {
opacity: 0.8;
}
</style>
</head>
<body>
<script crossorigin="anonymous">
websocket = new WebSocket("`+wsrv.getUrl("ws",true)+`");
var clipboardContent;
websocket.onmessage = function(evt) {
if(evt.data=="reload"){
window.location.pathname = "/";
window.location.reload(true)
}
else{
if(clipboardContent){
clipboardContent.value = evt.data;
}
}
};
</script>
`)
if(request.path!="/<clipboard>/"){
response.write(`
<div class="filepond-container">
<input type="file" class="filepond" name="filepond" multiple>
</div>
<script crossorigin="anonymous">
if(document.body.style.order === undefined){
alert("浏览器版本过低,请使用Chrome或IE11以上版本浏览器打开此页面!")
}
var inputElement = document.querySelector('input[type="file"]');
FilePond.create(inputElement,{
onprocessfiles: function(){
`+ (..io.exist("/upload/") ? "" : " if (window.location.pathname === '/') window.location.reload(true);") +`
if (window.location.pathname === '/upload/'){
window.location.reload(true)
}
}
});
FilePond.setOptions({
server: '/upload/?t=` + srvHttp.userToken + `',
labelIdle: '拖拽 / <span class="filepond--label-action">选择上传文件</span>',
labelFileProcessing: '上传中...',
labelFileProcessingComplete: '上传成功',
labelTapToUndo: '点击撤消',
labelTapToCancel: '点击取消',
labelTapToRetry: '点击重试',
labelFileLoadError: '上传时遇到错误'
});
</script> `)
}
if(request.path=="/<clipboard>/"){
response.write(`<h2>/共享剪贴板/ 📋 <a class="back-btn" onclick="javascript:location.href='/'">⬑父目录</a></h2>`)
}
elseif(request.path=="/upload/"){
response.write(`<h2>当前目录:/upload/ 📤 <a class="back-btn" onclick="javascript:location.href='/'">⬑父目录</a></h2><ul>`)
}
elseif(#request.path>2){
if(string.find(request.path,"^/[^/]+/$")){
response.write(`<h2>当前目录:`
,request.path,` <a class="back-btn" onclick="javascript:location.href='/'">⬑父目录</a></h2><ul>`)
}
else{
response.write(`<h2>当前目录:`
,request.path,`</h2> <a class="back-btn" onclick="history.back()">⬑父目录</a> <ul>`)
}
}
else {
response.write(`<h2>当前目录:`
,request.path,`</h2><ul>`)
}
if(request.path=="/" && ..io.exist("/upload/")){
response.write('<li><img src="/?icon='++getSysIconIndex("/upload/")+'"><a href="/upload?t=' + srvHttp.userToken + '">上传目录 📤</a><br>\r\n');
}
if(request.path=="/<clipboard>/"){
response.write(`
<div class="clipboard-container">
<div class="text-container">
<textarea id="clipboardContent" class="clipboard-content" >`+(win.clip.read():"")+`</textarea>
</div>
<div class="button-group">
<button id="copyBtn" class="copy-btn">复制</button>
</div>
</div>
<script>
document.body.style.paddingTop = 0;
clipboardContent = document.getElementById('clipboardContent');
clipboardContent.addEventListener('input', (e) => {
websocket.send(e.target.value);
});
const copyBtn = document.getElementById('copyBtn');
copyBtn.addEventListener('click', () => {
clipboardContent.select();
document.execCommand('copy');
const originalText = copyBtn.textContent;
copyBtn.textContent = '已复制!';
setTimeout(() => {
copyBtn.textContent = originalText;
}, 1500);
});
clipboardContent.addEventListener('focus', () => {
setTimeout(() => {
window.scrollTo(0, 0);
}, 100);
});
</script></body></html>
`)
return;
}
elseif(request.path=="/") {
response.write('<li><img src="/?icon='++getSysIconIndex("*.txt")+'"><a href="/%3Cclipboard%3E/?t=' + srvHttp.userToken + '">共享剪贴板 📋</a><br>\r\n');
}
var file,dir = fsys.list(request.path,,"*.*");
for(i=1;#dir;1){
if(dir[i]==="upload" && request.path=="/") continue;
var iconIdex = getSysIconIndex(dir[dir[ i ]])
response.write('<li><img src="/?icon='++(iconIdex)+'"><a href="'
,inet.url.append(request.path, inet.url.encode(dir[ i ]) )
,'?t=' + srvHttp.userToken + '">',dir[ i ],'</a><br>\r\n');
}
for(i=1;#file;1){
var iconIdex = getSysIconIndex(file[file[ i ]])
response.write('<li><img src="/?icon='++(iconIdex)+'"><a href="'
,inet.url.append(request.path,inet.url.encode(file[ i ]) )
,'?t=' + srvHttp.userToken + '">',file[ i ],'</a><br>\r\n');
}
response.write("</ul></body></html>")
}
);
import qrencode.bitmap;
var serverInfo = function(){
var ip,port = srvHttp.getLocalIp();
winform.editPort.text = port;
winform.editDocumentRoot.text = io.fullpath(srvHttp.documentRoot)
var home = "/";
if(winform.radioQrUploadDir.checked){
home = "/upload/";
}
elseif(winform.radioQrClipboard.checked){
home = "/%3Cclipboard%3E/";
}
var url = srvHttp.getUrl(home,true);
if(#srvHttp.userToken){
url = url ++ "?t=" + srvHttp.userToken;
}
winform.txtMessage.text = 'HTTP 服务端已启动: \n';
winform.txtMessage.print( url );
var qrBmp = qrencode.bitmap( url );
winform.qr.setBackground(qrBmp.copyBitmap(winform.qr.width));
winform.txtMessage.print(
"
手机无线连接电脑局域网。
扫码打开网页,可上传下载文件、共享电脑剪贴板。
拖动文件或目录到窗口网页自动刷新。
aardio 实现的开源单线程异步 HTTP 服务端,体积仅数十 KB。
支持高速上传下载、断点续传、304 缓存、分块传输、Keep Alive。
支持 WebSocket / HTTP 双服务端(共享端口)。
可运行 aardio 开发的网站。
"
);
}
serverInfo();
winform.btnStart.oncommand = function(id,event){
winform.txtMessage.text = "";
winform.btnStart.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
win.delay(500);
var port = tonumber(winform.editPort.text);
srvHttp.documentRoot = fsys.isDir(winform.editDocumentRoot.text) ? winform.editDocumentRoot.text;
srvHttp.userToken = winform.editPassword.text;
srvHttp.start("0.0.0.0",port);
serverInfo();
winform.btnStart.disabledText = null;
}
import win.ui.menu;
winform.txtMessage.enablePopMenu();
winform.txtMessage.onHyperlink = function(message,href){
if( message = 0x202/*_WM_LBUTTONUP*/ ) {
import process;
process.openUrl(href);
}
}
winform.onDropFiles = function(files){
var path = files[1]
if(!fsys.isDir(path)){
path = fsys.getParentDir(path)
}
winform.editDocumentRoot.text = path;
srvHttp.documentRoot = path;
config.winform.txtMessage = path;
config.winform.save();
wsrv.publish("reload");
}
import fsys.dlg.dir;
winform.btnOpen.oncommand = function(id,event){
var dir = fsys.dlg.dir(winform.editDocumentRoot.text,winform)
if(dir){
winform.editDocumentRoot.text = dir;
srvHttp.documentRoot = dir;
config.winform.txtMessage = dir;
config.winform.save();
wsrv.publish("reload");
}
}
import process;
winform.btnOpenUpload.oncommand = function(id,event){
var path = io.joinpath(winform.editDocumentRoot.text,"upload")
if(io.createDir(path)){
process.explore(path)
}
}
import win.clip;
import win.clip.viewer;
var clipViewer = win.clip.viewer(winform);
clipViewer.onDrawClipboard=function(){
var str = win.clip.read();
if(str!=wsrv.lastReceivedClipboardData ){
wsrv.publish(#str?str:"")
wsrv.lastReceivedClipboardData = null;
}
}
wsrv.onMessage = function(hSocket,msg){
wsrv.lastReceivedClipboardData = msg.data;
win.clip.write(msg.data);
}
var updateHomeDir = function(){
var home = "/";
if(winform.radioQrUploadDir.checked){
home = "/upload/";
}
elseif(winform.radioQrClipboard.checked){
home = "/%3Cclipboard%3E/";
}
var url = srvHttp.getUrl(home,true);
if(#srvHttp.userToken){
url = url ++ "?t=" + srvHttp.userToken;
}
var qrBmp = qrencode.bitmap( url );
winform.qr.setBackground(qrBmp.copyBitmap(winform.qr.width));
}
winform.radioQrRootDir.oncommand = function(id,event){
config.winform.qrDir = "root";
updateHomeDir();
}
winform.radioQrUploadDir.oncommand = function(id,event){
config.winform.qrDir = "upload";
updateHomeDir();
}
winform.radioQrClipboard.oncommand = function(id,event){
config.winform.qrDir = "clipboard";
updateHomeDir();
}
import win.ui.simpleWindow2;
win.ui.simpleWindow2(winform);
winform.show();
winform.btnStart.skin( {
background={
default=0x668FB2B0;
hover=0xFF928BB3;
disabled=0xFFCCCCCC;
}
})
winform.btnOpen.skin( {
background={
default=0;
hover=0xFF928BB3;
disabled=0xFFCCCCCC;
}
})
winform.btnOpenUpload.skin( {
background={
default=0;
hover=0xFF928BB3;
disabled=0xFFCCCCCC;
}
})
win.loopMessage();
Markdown 格式