# aardio 范例: WebDAV 服务器 - 图形界面版

```aardio

import win.ui;
import fsys.dlg;
import fonts.fontAwesome;
/*DSG{{*/
mainForm = win.form(text="WebDAV 服务器 - 图形界面版";right=805;bottom=584;bgcolor=0xF5F7FA;border="dialog frame";max=false)
mainForm.add(
bgConfig={cls="bkplus";left=20;top=20;right=779;bottom=280;bgcolor=0xFFFFFF;border=1;dl=1;dr=1;dt=1;z=1};
bgLog={cls="bkplus";left=20;top=300;right=779;bottom=560;bgcolor=0xFFFFFF;border=1;db=1;dl=1;dr=1;dt=1;z=2};
btnBrowse={cls="plus";text="浏览";left=516;top=113;right=586;bottom=143;dr=1;dt=1;font=LOGFONT(h=-13);z=11};
btnClear={cls="plus";text="清空";left=698;top=310;right=748;bottom=335;dr=1;dt=1;z=20};
btnRestart={cls="plus";text="重启";left=526;top=210;right=586;bottom=250;dr=1;dt=1;font=LOGFONT(h=-13);notify=1;z=18};
btnStart={cls="plus";text="启动服务";left=413;top=210;right=503;bottom=250;dr=1;dt=1;font=LOGFONT(h=-13);notify=1;z=17};
chkPublicAddress={cls="plus";text="公网 IP";left=615;top=254;right=720;bottom=285;align="left";font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome')};iconText='\uF0C8 ';notify=1;textPadding={left=24};z=24};
editIp={cls="edit";text="0.0.0.0";left=105;top=66;right=368;bottom=96;dl=1;dt=1;edge=1;font=LOGFONT(h=-13);z=5};
editLog={cls="richedit";left=40;top=345;right=748;bottom=540;db=1;dl=1;dr=1;dt=1;edge=1;font=LOGFONT(name='Consolas');link=1;multiline=1;readonly=1;vscroll=1;z=21};
editPass={cls="edit";text="123456";left=446;top=158;right=586;bottom=188;dr=1;dt=1;edge=1;font=LOGFONT(h=-13);password=1;z=15};
editPort={cls="edit";text="8081";left=446;top=66;right=586;bottom=96;dr=1;dt=1;edge=1;font=LOGFONT(h=-13);num=1;z=7};
editRoot={cls="edit";left=105;top=112;right=508;bottom=142;dl=1;dr=1;dt=1;edge=1;font=LOGFONT(h=-13);z=23};
editThread={cls="edit";text="10";left=680;top=66;right=748;bottom=96;dr=1;dt=1;edge=1;font=LOGFONT(h=-13);num=1;z=9};
editUser={cls="edit";text="admin";left=105;top=158;right=368;bottom=188;dl=1;dt=1;edge=1;font=LOGFONT(h=-13);z=13};
lblConfig={cls="bkplus";text="服务配置";left=40;top=30;right=150;bottom=55;color=0x333333;dl=1;dt=1;font=LOGFONT(h=-16;weight=600);z=3};
lblIp={cls="bkplus";text="监听 IP：";left=18;top=69;right=98;bottom=94;align="right";color=0x666666;dl=1;dt=1;font=LOGFONT(h=-13);z=4};
lblLog={cls="bkplus";text="运行日志";left=40;top=310;right=150;bottom=335;color=0x333333;db=1;dl=1;dt=1;font=LOGFONT(h=-16;weight=600);z=19};
lblPass={cls="bkplus";text="密码：";left=379;top=161;right=439;bottom=186;align="right";color=0x666666;dl=1;dr=1;dt=1;font=LOGFONT(h=-13);z=14};
lblPort={cls="bkplus";text="端口：";left=379;top=69;right=439;bottom=94;align="right";color=0x666666;dl=1;dr=1;dt=1;font=LOGFONT(h=-13);z=6};
lblRoot={cls="bkplus";text="根目录：";left=18;top=115;right=98;bottom=140;align="right";color=0x666666;dl=1;dt=1;font=LOGFONT(h=-13);z=10};
lblStatus={cls="plus";text='\u25CF 未启动';left=40;top=213;right=200;bottom=243;align="left";color=0x999999;dl=1;dt=1;font=LOGFONT(h=-14;weight=600);iconStyle={align="left";font=LOGFONT(name='FontAwesome');padding={left=10}};iconText='\uF111';textPadding={left=30};z=16};
lblThread={cls="bkplus";text="线程数：";left=600;top=69;right=670;bottom=94;align="right";color=0x666666;dr=1;dt=1;font=LOGFONT(h=-13);z=8};
lblUser={cls="bkplus";text="用户名：";left=18;top=161;right=98;bottom=186;align="right";color=0x666666;dl=1;dt=1;font=LOGFONT(h=-13);z=12};
qr={cls="plus";text='\uF029';left=610;top=112;right=748;bottom=250;border={color=0xFF808080;radius=8;width=1};db=1;dr=1;dt=1;font=LOGFONT(h=-22;name='FontAwesome');foreRepeat="scale";repeat="scale";z=22}
)
/*}}*/

// 全局变量
var isRunning = false;
var webdavServerId = null;

// 更新状态显示
var updateStatus = function(status, color){
    mainForm.lblStatus.text = "● " + status;
    mainForm.lblStatus.color = color;
}

// 浏览目录
mainForm.btnBrowse.oncommand = function(id,event){
    import fsys.dlg.dir;
    var dir = fsys.dlg.dir(mainForm.editRoot.text, mainForm, "选择 WebDAV 根目录");
    if(dir){
        mainForm.editRoot.text = dir;
    }
}
mainForm.editRoot.text = io.fullpath("/")

// 清空日志
mainForm.btnClear.oncommand = function(id,event){
    mainForm.editLog.text = "";
}

// 停止服务
var stopServer = function(){
    if(mainForm.webdavServerId){
        import wsock.tcp.server;
        wsock.tcp.server.closeById(mainForm.webdavServerId);
        mainForm.webdavServerId = null;
        isRunning = false;
        updateStatus("已停止", 0xFF999999);
        mainForm.btnStart.text = "启动服务";
        mainForm.editLog.print("[" + tostring(time(), "%H:%M:%S") + "] 服务已停止");
    }
}

// 启动服务
var startServer = function(){
	mainForm.editLog.text = "";
	
    var ip = mainForm.editIp.text;
    var port = tonumber(mainForm.editPort.text);
    var threadNum = tonumber(mainForm.editThread.text);
    var rootDir = mainForm.editRoot.text;
    var username = mainForm.editUser.text;
    var password = mainForm.editPass.text;

    // 验证输入
    if(!port || port < 1 || port > 65535){
        mainForm.msgbox("请输入有效的端口号 (1-65535)", "错误", "error");
        return;
    }

    if(!threadNum || threadNum < 1){
        mainForm.msgbox("请输入有效的线程数 (≥1)", "错误", "error");
        return;
    }

    if(!#rootDir){
        mainForm.msgbox("请选择 WebDAV 根目录", "错误", "error");
        return;
    }

    import fsys;
    if(!fsys.isDir(rootDir)){
        mainForm.msgbox("指定的根目录不存在", "错误", "error");
        return;
    }

    if(!#username || !#password){
        mainForm.msgbox("请输入用户名和密码", "错误", "error");
        return;
    }

    // 启动服务
    thread.invoke(
        function(mainForm, ip, port, threadNum, rootDir, username, password){
            import web.dav.server;

            var webdav = web.dav.server(ip, tonumber(port));
            webdav.threadNum = threadNum;
            webdav.resourceRoot = rootDir;

            mainForm.webdavServerId = webdav.id;

            var url = webdav.getUrl(true); 
            
            var tUrl = inet.url.split(url);
            if(mainForm.chkPublicAddress.checked){
            	import wsock.udp.stunClient;
            	var sutn = wsock.udp.stunClient();
            	var resp = sutn.getPublicAddress();
            	if(resp){
            		tUrl.host = resp.ip;
            		url = tostring(tUrl);
            	}
            }
            
            tUrl.user = username;
			tUrl.password = password;
			mainForm.qr.setUrl( tostring(tUrl) ); 
          
            mainForm.editLog.print("[" + tostring(time(), "%H:%M:%S") + "] 服务已启动: " + url + " 手机扫码可连接");
            mainForm.editLog.print("[" + tostring(time(), "%H:%M:%S") + "] 根目录: " + rootDir);
            mainForm.editLog.print("[" + tostring(time(), "%H:%M:%S") + "] 线程数: " + threadNum);
			 
			import win.ts;
			if(win.ts.session.isRemote()){
				mainForm.editLog.appendLink("关闭远程桌面并继续运行服务");
			}
			
            webdav.threadGlobal = {
                mainForm = mainForm;
                authData = [username, password]
            }

            webdav.run(
                function(response, request, session){
                    
                    // HTTP 认证
                    if(!request.auth(
                        function(user){ 
                            if(user == ..authData[1]) return ..authData[2];
                        }, "WebDAV"
                    )){
                        return false;
                    }
                    
                  
                   	// WebDAV 不可能向目录发送 GET 请求
                   	if(request.method=="GET" && string.endsWith(request.path,"/")){
                   	    // 返回 WebDAV 客户端 Web 版
                   		response.write($"~\lib\web\dav\webdav.html")
                    	return false;	
                   	}

                    // 记录请求
                    ..mainForm.editLog.print(
                        "[" + tostring(time(), "%H:%M:%S") + "] " + 
                        request.method + " " + request.path
                    );
                }
            )

            mainForm.editLog.print("[" + tostring(time(), "%H:%M:%S") + "] 服务已退出");
        },
        mainForm, ip, port, threadNum, rootDir, username, password
    )

    isRunning = true;
    webdavServerId = mainForm.webdavServerId;
    updateStatus("运行中", 0xFF4CAF50);
    mainForm.btnStart.text = "停止服务";
    mainForm.editLog.print("[" + tostring(time(), "%H:%M:%S") + "] 正在启动服务...");
}

mainForm.editLog.onHyperlink =function(message,href){
 
	if( message = 0x202/*_WM_LBUTTONUP*/ ) {
		if(href=="关闭远程桌面并继续运行服务"){
			import win.ts;
			win.ts.session().connect();
			return;
		}
		raw.execute(href);
	}
}

mainForm.qr.setUrl = function(url){
    import qrencode.bitmap
    var qrBmp = qrencode.bitmap( url );
    
    mainForm.qr.border = null;
    mainForm.qr.text = null;
    mainForm.qr.setForeground(qrBmp.copyBitmap(mainForm.qr.width));
}

// 启动/停止按钮
mainForm.btnStart.oncommand = function(id,event){
    if(isRunning){
        stopServer();
    } else {
        startServer();
    }
}

import win.version;
if(win.version.isServer){
	import win.ts; 
	if( win.ts.session.isRemote() ){
		// 检测到当前是通过远程桌面登录的服务器环境，默认使用公网 IP
    	mainForm.chkPublicAddress.checked = true; 
    	
    	import wsock.tcp.server;
    	if( wsock.tcp.server.isFreePort(80)) mainForm.editPort.text = 80;
	} 
}

// 重启按钮
mainForm.btnRestart.oncommand = function(id,event){
    if(isRunning){
        stopServer();
        thread.delay(500);
    }
    startServer();
}

// 按钮样式
mainForm.btnStart.skin({
    background={default=0xFF4CAF50;hover=0xFF45A049;active=0xFF3D8B40};
    color={default=0xFFFFFFFF};
    border={default={radius=6}}
});

mainForm.btnRestart.skin({
    background={default=0xFFFF9800;hover=0xFFFB8C00;active=0xFFE67E00};
    color={default=0xFFFFFFFF};
    border={default={radius=6}}
});

mainForm.btnBrowse.skin({
    background={default=0xFF2196F3;hover=0xFF1E88E5;active=0xFF1976D2};
    color={default=0xFFFFFFFF};
    border={default={radius=6}}
});

mainForm.btnClear.skin({
    background={default=0xFFEEEEEE;hover=0xFFE0E0E0;active=0xFFD0D0D0};
    color={default=0xFF666666};
    border={default={radius=4}}
});

mainForm.chkPublicAddress.skin({
	color={
		active=0xFF00FF00;
		default=0xFF000000;
		disabled=0xEE666666;
		hover=0xFFFF0000		
	};
	checked={
		iconText='\uF14A'		
	}
})

// 窗口退出时停止服务
mainForm.onDestroy = function(){
    if(isRunning){
        stopServer();
    }
}

mainForm.chkPublicAddress.oncommand = function(id,event){
	mainForm.btnRestart.oncommand();
}

import win.ts.changeNotification;
var changeNotification = win.ts.changeNotification(winform);
changeNotification.onSessionChange = function(sessionId,statusText,statusCode){
	if(statusCode&1) mainForm.enableDpiScaling(); //恢复远程桌面会话时必须更新 DPI 缩放大小
} 

mainForm.show();
win.loopMessage();
```