|
@@ -0,0 +1,680 @@
|
|
|
|
|
+// rcp通信工具
|
|
|
|
|
+import { BusinessError } from '@kit.BasicServicesKit';
|
|
|
|
|
+import { buffer, HashMap, JSON, util, xml } from '@kit.ArkTS';
|
|
|
|
|
+import { FileInfo } from '../../viewmodel/FileInfo';
|
|
|
|
|
+import { rcp } from '@kit.RemoteCommunicationKit';
|
|
|
|
|
+import { BackgroundManager } from './BackgroundManager';
|
|
|
|
|
+import FileManager, { merge2paths } from './FileManager';
|
|
|
|
|
+
|
|
|
|
|
+const UtilName = "heanup RcpSocket"
|
|
|
|
|
+
|
|
|
|
|
+export class RcpSocket {
|
|
|
|
|
+ private static instance: RcpSocket;
|
|
|
|
|
+ private backgroundManager = BackgroundManager.getInstance()
|
|
|
|
|
+ public ErrorMessage: string | BusinessError = ''
|
|
|
|
|
+ public filesInfo: FileInfo[] = []
|
|
|
|
|
+
|
|
|
|
|
+ //private rcpSession : rcp.Session | null = null
|
|
|
|
|
+
|
|
|
|
|
+ public RcpSendHead(host: string, port: number, account: string, password: string, path: string,
|
|
|
|
|
+ enableHttps: boolean): Promise<number> {
|
|
|
|
|
+ return new Promise<number>((resolve, reject) => {
|
|
|
|
|
+ const url = `${enableHttps ? "https" : "http"}://${host}:${port}${path}`;
|
|
|
|
|
+ const timeoutDuration: number = 10000;
|
|
|
|
|
+ const speedThreshold: number = 5000; // 设置速度测试的时间阈值
|
|
|
|
|
+ console.info(UtilName, 'testTag', '发送HEAD的url:' + url)
|
|
|
|
|
+ // 创建 RCP 会话配置
|
|
|
|
|
+ let secCfg: rcp.SecurityConfiguration = { remoteValidation: 'skip' }
|
|
|
|
|
+ let reqCfg: rcp.Configuration = {
|
|
|
|
|
+ security: secCfg,
|
|
|
|
|
+ transfer: {
|
|
|
|
|
+ timeout: {
|
|
|
|
|
+ connectMs: timeoutDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ let sessionCfg: rcp.SessionConfiguration = { requestConfiguration: reqCfg }
|
|
|
|
|
+ let rcpSession = rcp.createSession(sessionCfg);
|
|
|
|
|
+ // 构造基本认证头部
|
|
|
|
|
+ const encodedCredentials = buffer
|
|
|
|
|
+ .from(`${account}:${password}`)
|
|
|
|
|
+ .toString("base64");
|
|
|
|
|
+
|
|
|
|
|
+ const headers: rcp.RequestHeaders = {
|
|
|
|
|
+ Depth: "1",
|
|
|
|
|
+ "Content-Type": "application/xml",
|
|
|
|
|
+ Accept: "text/xml",
|
|
|
|
|
+ Authorization: `Basic ${encodedCredentials}`,
|
|
|
|
|
+ };
|
|
|
|
|
+ // 创建请求对象
|
|
|
|
|
+ const req = new rcp.Request(url, "HEAD", headers);
|
|
|
|
|
+ const startTime = Date.now();
|
|
|
|
|
+ // 连接
|
|
|
|
|
+ try {
|
|
|
|
|
+ rcpSession
|
|
|
|
|
+ .fetch(req)
|
|
|
|
|
+ .then((response) => {
|
|
|
|
|
+ const endTime = Date.now();
|
|
|
|
|
+ const elapsedTime = endTime - startTime;
|
|
|
|
|
+ // 如果响应时间超过阈值,视为测速过慢
|
|
|
|
|
+ if (elapsedTime > speedThreshold) {
|
|
|
|
|
+ console.error(UtilName, "testTag", `${host}Response time too slow: ${elapsedTime}ms`);
|
|
|
|
|
+ rcpSession?.close()
|
|
|
|
|
+ reject("Test speed too slow");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.info(UtilName, "testTag", host + " Connect and test speed succeed");
|
|
|
|
|
+ const contentLength = response.headers['content-length'] || '0'
|
|
|
|
|
+ let fileSize = 0
|
|
|
|
|
+ if (contentLength) {
|
|
|
|
|
+ if (Array.isArray(contentLength)) {
|
|
|
|
|
+ const values = contentLength
|
|
|
|
|
+ .map((value) => parseInt(value, 10))
|
|
|
|
|
+ .filter((value) => !isNaN(value));
|
|
|
|
|
+ if (values.length > 0) {
|
|
|
|
|
+ // 取最大值
|
|
|
|
|
+ fileSize = Math.max(...values);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ fileSize = parseInt(contentLength, 10);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.info(UtilName, 'testTag', 'Content-Length 头未找到');
|
|
|
|
|
+ }
|
|
|
|
|
+ rcpSession?.close();
|
|
|
|
|
+ resolve(fileSize);
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err: BusinessError) => {
|
|
|
|
|
+ console.error(UtilName, "testTag", `${host}请求失败:错误码 ${err.code},错误信息:${JSON.stringify(err)}`);
|
|
|
|
|
+ rcpSession?.close()
|
|
|
|
|
+ reject(err);
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error(UtilName, 'testTag', '发起连接失败', JSON.stringify(e))
|
|
|
|
|
+ reject(e)
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public RcpSendDelete(host: string, port: number, account: string, password: string, path: string,
|
|
|
|
|
+ enableHttps: boolean): Promise<void> {
|
|
|
|
|
+ return new Promise<void>((resolve, reject) => {
|
|
|
|
|
+ const url = `${enableHttps ? "https" : "http"}://${host}:${port}${path}`;
|
|
|
|
|
+ const timeoutDuration: number = 10000;
|
|
|
|
|
+ console.info(UtilName, 'testTag', '发送Delete的url:' + url)
|
|
|
|
|
+ // 创建 RCP 会话配置
|
|
|
|
|
+ let response = ""
|
|
|
|
|
+ const customHttpEventsHandler: rcp.HttpEventsHandler = {
|
|
|
|
|
+ onDataReceive: (incomingData: ArrayBuffer) => {
|
|
|
|
|
+ response += this.buf2String(incomingData)
|
|
|
|
|
+ },
|
|
|
|
|
+ onDataEnd: () => {
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const tracingConfig: rcp.TracingConfiguration = {
|
|
|
|
|
+ verbose: true,
|
|
|
|
|
+ infoToCollect: {
|
|
|
|
|
+ textual: true,
|
|
|
|
|
+ incomingData: true,
|
|
|
|
|
+ outgoingData: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ collectTimeInfo: true,
|
|
|
|
|
+ httpEventsHandler: customHttpEventsHandler
|
|
|
|
|
+ };
|
|
|
|
|
+ let secCfg: rcp.SecurityConfiguration = { remoteValidation: 'skip' }
|
|
|
|
|
+ let reqCfg: rcp.Configuration = {
|
|
|
|
|
+ security: secCfg,
|
|
|
|
|
+ tracing: tracingConfig,
|
|
|
|
|
+ transfer: {
|
|
|
|
|
+ timeout: {
|
|
|
|
|
+ connectMs: timeoutDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ let sessionCfg: rcp.SessionConfiguration = { requestConfiguration: reqCfg }
|
|
|
|
|
+ let rcpSession = rcp.createSession(sessionCfg);
|
|
|
|
|
+ // 构造基本认证头部
|
|
|
|
|
+ const encodedCredentials = buffer
|
|
|
|
|
+ .from(`${account}:${password}`)
|
|
|
|
|
+ .toString("base64");
|
|
|
|
|
+
|
|
|
|
|
+ const headers: rcp.RequestHeaders = {
|
|
|
|
|
+ Authorization: `Basic ${encodedCredentials}`,
|
|
|
|
|
+ };
|
|
|
|
|
+ // 创建请求对象
|
|
|
|
|
+ const req = new rcp.Request(url, "DELETE", headers);
|
|
|
|
|
+ // 连接
|
|
|
|
|
+ rcpSession
|
|
|
|
|
+ .fetch(req)
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ console.info(UtilName, 'testTag', '执行删除的响应', JSON.stringify(response))
|
|
|
|
|
+ rcpSession.close()
|
|
|
|
|
+ resolve()
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err: BusinessError) => {
|
|
|
|
|
+ console.error(UtilName, "testTag", `${host}请求失败:错误码 ${err.code},错误信息:${JSON.stringify(err)}`);
|
|
|
|
|
+ rcpSession?.close()
|
|
|
|
|
+ reject(err);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public RcpSendMove(host: string, port: number, account: string, password: string, path: string, enableHttps: boolean,
|
|
|
|
|
+ newPath: string): Promise<void> {
|
|
|
|
|
+ return new Promise<void>((resolve, reject) => {
|
|
|
|
|
+ const url = `${enableHttps ? "https" : "http"}://${host}:${port}${path}`;
|
|
|
|
|
+ const destinationUrl = `${enableHttps ? "https" : "http"}://${host}:${port}${newPath}`
|
|
|
|
|
+ const timeoutDuration: number = 10000;
|
|
|
|
|
+ console.info(UtilName, 'testTag', '发送MOVE的url:' + url)
|
|
|
|
|
+ // 创建 RCP 会话配置
|
|
|
|
|
+ let response = ""
|
|
|
|
|
+ const customHttpEventsHandler: rcp.HttpEventsHandler = {
|
|
|
|
|
+ onDataReceive: (incomingData: ArrayBuffer) => {
|
|
|
|
|
+ response += this.buf2String(incomingData)
|
|
|
|
|
+ },
|
|
|
|
|
+ onDataEnd: () => {
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const tracingConfig: rcp.TracingConfiguration = {
|
|
|
|
|
+ verbose: true,
|
|
|
|
|
+ infoToCollect: {
|
|
|
|
|
+ textual: true,
|
|
|
|
|
+ incomingData: true,
|
|
|
|
|
+ outgoingData: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ collectTimeInfo: true,
|
|
|
|
|
+ httpEventsHandler: customHttpEventsHandler
|
|
|
|
|
+ };
|
|
|
|
|
+ let secCfg: rcp.SecurityConfiguration = { remoteValidation: 'skip' }
|
|
|
|
|
+ let reqCfg: rcp.Configuration = {
|
|
|
|
|
+ security: secCfg,
|
|
|
|
|
+ tracing: tracingConfig,
|
|
|
|
|
+ transfer: {
|
|
|
|
|
+ timeout: {
|
|
|
|
|
+ connectMs: timeoutDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ let sessionCfg: rcp.SessionConfiguration = { requestConfiguration: reqCfg }
|
|
|
|
|
+ let rcpSession = rcp.createSession(sessionCfg);
|
|
|
|
|
+ // 构造基本认证头部
|
|
|
|
|
+ const encodedCredentials = buffer
|
|
|
|
|
+ .from(`${account}:${password}`)
|
|
|
|
|
+ .toString("base64");
|
|
|
|
|
+
|
|
|
|
|
+ const headers: rcp.RequestHeaders = {
|
|
|
|
|
+ Authorization: `Basic ${encodedCredentials}`,
|
|
|
|
|
+ Destination: destinationUrl
|
|
|
|
|
+ };
|
|
|
|
|
+ // 创建请求对象
|
|
|
|
|
+ const req = new rcp.Request(url, "MOVE", headers);
|
|
|
|
|
+ // 连接
|
|
|
|
|
+ rcpSession
|
|
|
|
|
+ .fetch(req)
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ console.info(UtilName, 'testTag', 'move的响应结果', JSON.stringify(response))
|
|
|
|
|
+ rcpSession.close()
|
|
|
|
|
+ resolve()
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err: BusinessError) => {
|
|
|
|
|
+ console.error(UtilName, "testTag", `${host}请求失败:错误码 ${err.code},错误信息:${JSON.stringify(err)}`);
|
|
|
|
|
+ rcpSession.close()
|
|
|
|
|
+ reject(err);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // rcp方法
|
|
|
|
|
+ public async RcpSendPropFind(
|
|
|
|
|
+ host: string,
|
|
|
|
|
+ port: number,
|
|
|
|
|
+ account: string,
|
|
|
|
|
+ password: string,
|
|
|
|
|
+ path: string,
|
|
|
|
|
+ enableHttps: boolean
|
|
|
|
|
+ ): Promise<FileInfo[]> {
|
|
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
|
|
+ const url = `${enableHttps ? "https" : "http"}://${host}:${port.toString()}${path}`;
|
|
|
|
|
+ const timeoutDuration: number = 10000;
|
|
|
|
|
+ console.info(UtilName, 'testTag', '发送PROPFIND请求的url:' + url)
|
|
|
|
|
+ // 创建 RCP 会话配置
|
|
|
|
|
+ let response: string = ''
|
|
|
|
|
+ const customHttpEventsHandler: rcp.HttpEventsHandler = {
|
|
|
|
|
+ onDataReceive: async (incomingData: ArrayBuffer) => {
|
|
|
|
|
+ response += this.buf2String(incomingData)
|
|
|
|
|
+ await this.backgroundManager.updateDataTransferContinuousTask()
|
|
|
|
|
+ },
|
|
|
|
|
+ onDataEnd: () => {
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const tracingConfig: rcp.TracingConfiguration = {
|
|
|
|
|
+ verbose: true,
|
|
|
|
|
+ infoToCollect: {
|
|
|
|
|
+ textual: true,
|
|
|
|
|
+ incomingData: true,
|
|
|
|
|
+ outgoingData: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ collectTimeInfo: true,
|
|
|
|
|
+ httpEventsHandler: customHttpEventsHandler
|
|
|
|
|
+ };
|
|
|
|
|
+ let secCfg: rcp.SecurityConfiguration = { remoteValidation: 'skip' }
|
|
|
|
|
+ let reqCfg: rcp.Configuration = {}
|
|
|
|
|
+ if (enableHttps) {
|
|
|
|
|
+ reqCfg = {
|
|
|
|
|
+ security: secCfg,
|
|
|
|
|
+ tracing: tracingConfig,
|
|
|
|
|
+ transfer: {
|
|
|
|
|
+ timeout: {
|
|
|
|
|
+ connectMs: timeoutDuration,
|
|
|
|
|
+ transferMs: timeoutDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ reqCfg = {
|
|
|
|
|
+ tracing: tracingConfig,
|
|
|
|
|
+ transfer: {
|
|
|
|
|
+ timeout: {
|
|
|
|
|
+ connectMs: timeoutDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let sessionCfg: rcp.SessionConfiguration = { requestConfiguration: reqCfg }
|
|
|
|
|
+ let rcpSession = rcp.createSession(sessionCfg);
|
|
|
|
|
+
|
|
|
|
|
+ // 构造 PROPFIND 请求体
|
|
|
|
|
+ const requestBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
+ <D:propfind xmlns:D="DAV:">
|
|
|
|
|
+ <D:prop>
|
|
|
|
|
+ <D:displayname/> <!-- 请求文件名 -->
|
|
|
|
|
+ <D:getcontentlength/> <!-- 请求文件大小 -->
|
|
|
|
|
+ <D:getlastmodified/> <!-- 请求文件最后修改时间 -->
|
|
|
|
|
+ </D:prop>
|
|
|
|
|
+ </D:propfind>`;
|
|
|
|
|
+
|
|
|
|
|
+ // 构造基本认证头部
|
|
|
|
|
+ const encodedCredentials = buffer
|
|
|
|
|
+ .from(`${account}:${password}`)
|
|
|
|
|
+ .toString("base64");
|
|
|
|
|
+ console.info(UtilName,'testTag',account,password)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ const headers: rcp.RequestHeaders = {
|
|
|
|
|
+ "Depth": "1",
|
|
|
|
|
+ "Content-Type": "application/xml",
|
|
|
|
|
+ "Accept": "text/xml",
|
|
|
|
|
+ "Authorization": `Basic ${encodedCredentials}`,
|
|
|
|
|
+ };
|
|
|
|
|
+ // 创建请求对象
|
|
|
|
|
+ const req = new rcp.Request(url, "PROPFIND", headers, requestBody);
|
|
|
|
|
+
|
|
|
|
|
+ // 发起请求
|
|
|
|
|
+ try {
|
|
|
|
|
+ await rcpSession.fetch(req)
|
|
|
|
|
+ .finally(() => {
|
|
|
|
|
+ console.info(UtilName, 'testTag', 'PROPFIND执行完毕')
|
|
|
|
|
+ if (response != '') {
|
|
|
|
|
+ // 提取文件信息
|
|
|
|
|
+ const filesInfo = this.extractHrefContents(response, path,url);
|
|
|
|
|
+ if (filesInfo.length !== 0) {
|
|
|
|
|
+ console.info(UtilName, 'testTag', '请求成功')
|
|
|
|
|
+ rcpSession.close()
|
|
|
|
|
+ resolve(filesInfo);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let message = `请求${host}失败,响应信息:${response}`
|
|
|
|
|
+ console.error(UtilName, 'testTag', message)
|
|
|
|
|
+ rcpSession.close()
|
|
|
|
|
+ reject(message)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let error = `服务器响应信息为空,请求失败`
|
|
|
|
|
+ console.error(UtilName, 'testTag', error)
|
|
|
|
|
+ rcpSession.close()
|
|
|
|
|
+ reject(error);
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ // 处理成功响应
|
|
|
|
|
+ //console.info(UtilName, 'testTag', JSON.stringify(res));
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ // 处理错误响应
|
|
|
|
|
+ console.error(UtilName, "testTag", `请求${url}失败:错误码 ${err.code},错误信息:${JSON.stringify(err)}`);
|
|
|
|
|
+ let error = `错误码${err.code},错误信息${err.data}`
|
|
|
|
|
+ rcpSession.close()
|
|
|
|
|
+ reject(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // PROPFIND递归方法
|
|
|
|
|
+ public async RcpSendPropFindInfinity(
|
|
|
|
|
+ host: string,
|
|
|
|
|
+ port: number,
|
|
|
|
|
+ account: string,
|
|
|
|
|
+ password: string,
|
|
|
|
|
+ path: string,
|
|
|
|
|
+ enableHttps: boolean,
|
|
|
|
|
+ useCache: boolean,
|
|
|
|
|
+ saveCache: boolean,
|
|
|
|
|
+ cachePath: string
|
|
|
|
|
+ ): Promise<FileInfo[]> {
|
|
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
|
|
+ const url = `${enableHttps ? "https" : "http"}://${host}:${port.toString()}${path}`;
|
|
|
|
|
+ const timeoutDuration: number = 10000;
|
|
|
|
|
+ console.info(UtilName, 'testTag', '发送PROPFIND递归请求的url:' + url)
|
|
|
|
|
+ let cacheFileInfos_str: string = ''
|
|
|
|
|
+ if (useCache) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ cacheFileInfos_str = await FileManager.readFileToString(cachePath)
|
|
|
|
|
+ if (cacheFileInfos_str) {
|
|
|
|
|
+ let files = JSON.parse(cacheFileInfos_str) as FileInfo[]
|
|
|
|
|
+ console.info(UtilName, 'testTag', '使用缓存')
|
|
|
|
|
+ resolve(files)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error(UtilName, 'testTag', '读取缓存失败', JSON.stringify(e))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 创建 RCP 会话配置
|
|
|
|
|
+ let response: string = ''
|
|
|
|
|
+ const customHttpEventsHandler: rcp.HttpEventsHandler = {
|
|
|
|
|
+ onDataReceive: async (incomingData: ArrayBuffer) => {
|
|
|
|
|
+ response += this.buf2String(incomingData)
|
|
|
|
|
+ //await this.backgroundManager.updateDataTransferContinuousTask(0,0,path)
|
|
|
|
|
+ },
|
|
|
|
|
+ onDataEnd: () => {
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const tracingConfig: rcp.TracingConfiguration = {
|
|
|
|
|
+ verbose: true,
|
|
|
|
|
+ infoToCollect: {
|
|
|
|
|
+ textual: true,
|
|
|
|
|
+ incomingData: true,
|
|
|
|
|
+ outgoingData: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ collectTimeInfo: true,
|
|
|
|
|
+ httpEventsHandler: customHttpEventsHandler
|
|
|
|
|
+ };
|
|
|
|
|
+ let secCfg: rcp.SecurityConfiguration = { remoteValidation: 'skip' }
|
|
|
|
|
+ let reqCfg: rcp.Configuration = {}
|
|
|
|
|
+ if (enableHttps) {
|
|
|
|
|
+ reqCfg = {
|
|
|
|
|
+ security: secCfg,
|
|
|
|
|
+ tracing: tracingConfig,
|
|
|
|
|
+ transfer: {
|
|
|
|
|
+ timeout: {
|
|
|
|
|
+ connectMs: timeoutDuration,
|
|
|
|
|
+ transferMs: timeoutDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ reqCfg = {
|
|
|
|
|
+ tracing: tracingConfig,
|
|
|
|
|
+ transfer: {
|
|
|
|
|
+ timeout: {
|
|
|
|
|
+ connectMs: timeoutDuration
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let sessionCfg: rcp.SessionConfiguration = { requestConfiguration: reqCfg }
|
|
|
|
|
+ let rcpSession = rcp.createSession(sessionCfg);
|
|
|
|
|
+
|
|
|
|
|
+ // 构造 PROPFIND 请求体
|
|
|
|
|
+ const requestBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
+ <D:propfind xmlns:D="DAV:">
|
|
|
|
|
+ <D:prop>
|
|
|
|
|
+ <D:displayname/> <!-- 请求文件名 -->
|
|
|
|
|
+ <D:getcontentlength/> <!-- 请求文件大小 -->
|
|
|
|
|
+ <D:getlastmodified/> <!-- 请求文件最后修改时间 -->
|
|
|
|
|
+ </D:prop>
|
|
|
|
|
+ </D:propfind>`;
|
|
|
|
|
+ // const requestBody = `<D:propfind xmlns:D="DAV:">
|
|
|
|
|
+ // <D:allprop/>
|
|
|
|
|
+ // </D:propfind>`;
|
|
|
|
|
+
|
|
|
|
|
+ // 构造基本认证头部
|
|
|
|
|
+ const encodedCredentials = buffer
|
|
|
|
|
+ .from(`${account}:${password}`)
|
|
|
|
|
+ .toString("base64");
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ const headers: rcp.RequestHeaders = {
|
|
|
|
|
+ "Depth": "1",
|
|
|
|
|
+ "Content-Type": "application/xml",
|
|
|
|
|
+ "Accept": "text/xml",
|
|
|
|
|
+ "Authorization": `Basic ${encodedCredentials}`,
|
|
|
|
|
+ };
|
|
|
|
|
+ let sendSinglePropfind = async (url: string, root: string): Promise<FileInfo[]> => {
|
|
|
|
|
+ return new Promise<FileInfo[]>(async (resolve, reject) => {
|
|
|
|
|
+ if(root.includes('%23recycle')){
|
|
|
|
|
+ resolve([])
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 发起请求
|
|
|
|
|
+ try {
|
|
|
|
|
+ AppStorage.setOrCreate('CurrentPropfindInfinityRoot',root)
|
|
|
|
|
+ response = ''
|
|
|
|
|
+ const req = new rcp.Request(url, "PROPFIND", headers, requestBody)
|
|
|
|
|
+ await rcpSession.fetch(req)
|
|
|
|
|
+ .finally(async () => {
|
|
|
|
|
+ if (response != '') {
|
|
|
|
|
+ // 提取文件信息
|
|
|
|
|
+ let filesInfo = this.extractHrefContents(response, root,url);
|
|
|
|
|
+ let folderInfos: FileInfo[] = []
|
|
|
|
|
+ for (const info of filesInfo) {
|
|
|
|
|
+ if (this.isFileFolder(info.name)) {
|
|
|
|
|
+ folderInfos.push(info)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ //console.info(UtilName,'testTag','PROPFIND执行完毕','根目录',root,'子目录数量',folderInfos.length)
|
|
|
|
|
+ if (folderInfos.length > 0) {
|
|
|
|
|
+ for (const folder of folderInfos) {
|
|
|
|
|
+ let sub_url = merge2paths(url, folder.name)
|
|
|
|
|
+ let sub_root = merge2paths(root, folder.name)
|
|
|
|
|
+ let next_infos = await sendSinglePropfind(sub_url, sub_root)
|
|
|
|
|
+ filesInfo = filesInfo.concat(next_infos)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filesInfo.length !== 0) {
|
|
|
|
|
+ resolve(filesInfo);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ resolve([])
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ resolve([])
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ // 处理错误响应
|
|
|
|
|
+ console.error(UtilName, "testTag", `请求${url}失败:错误码 ${err.code},错误信息:${JSON.stringify(err)}`);
|
|
|
|
|
+ let error = `错误码${err.code},错误信息${err.data}`
|
|
|
|
|
+ resolve([])
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ // 创建请求对象
|
|
|
|
|
+ await sendSinglePropfind(url, path)
|
|
|
|
|
+ .then(async (filesInfo: FileInfo[]) => {
|
|
|
|
|
+ console.info(UtilName, 'testTag', 'PROPFIND目录递归结果', filesInfo.length)
|
|
|
|
|
+ rcpSession?.close()
|
|
|
|
|
+ if (saveCache) {
|
|
|
|
|
+ let str = JSON.stringify(filesInfo)
|
|
|
|
|
+ if (str !== cacheFileInfos_str) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await FileManager.writeStringToFilePath(str, cachePath)
|
|
|
|
|
+ console.info(UtilName, 'testTag', '保存缓存成功')
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.info(UtilName, 'testTag', '保存缓存失败', JSON.stringify(e))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ resolve(filesInfo)
|
|
|
|
|
+ }).
|
|
|
|
|
+ catch((err: BusinessError) => {
|
|
|
|
|
+ rcpSession?.close()
|
|
|
|
|
+ console.error(UtilName,'testTag','PROPFIND目录递归失败',JSON.stringify(err))
|
|
|
|
|
+ reject(err)
|
|
|
|
|
+ })
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 判断文件是否属于文件夹
|
|
|
|
|
+ private isFileFolder(filename: string):boolean{
|
|
|
|
|
+ return filename.toLowerCase().endsWith('/')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ //ArrayBuffer转utf8字符串
|
|
|
|
|
+ buf2String(buf: ArrayBuffer) {
|
|
|
|
|
+ let msgArray = new Uint8Array(buf);
|
|
|
|
|
+ let textDecoder = util.TextDecoder.create("utf-8");
|
|
|
|
|
+ return textDecoder.decodeToString(msgArray)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取XML
|
|
|
|
|
+ extractXmlContent(httpResponse: string): string {
|
|
|
|
|
+ const xmlStart = httpResponse.indexOf('<?xml');
|
|
|
|
|
+ if (xmlStart !== -1) {
|
|
|
|
|
+ return httpResponse.substring(xmlStart);
|
|
|
|
|
+ }
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ stringToNumber(str: string): number {
|
|
|
|
|
+ let result:number = 0;
|
|
|
|
|
+ for (let i = 0; i < str.length; i++) {
|
|
|
|
|
+ result += str.charCodeAt(i);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 将 HTTP 日期字符串转换为 Unix 时间戳
|
|
|
|
|
+ private convertToUnixTimestamp(dateString: string): number {
|
|
|
|
|
+ const date = new Date(dateString);
|
|
|
|
|
+ if (isNaN(date.getTime())) {
|
|
|
|
|
+ console.error(UtilName,'testTag',"非法日期字符串:", dateString);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return Math.floor(date.getTime() / 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private decodeXMLEntities(str: string): string {
|
|
|
|
|
+ const entityMap: HashMap<string,string> = new HashMap()
|
|
|
|
|
+ entityMap.set('&', '&')
|
|
|
|
|
+ entityMap.set('<', '<')
|
|
|
|
|
+ entityMap.set('>','>')
|
|
|
|
|
+ entityMap.set('"', '"')
|
|
|
|
|
+ entityMap.set(''', "'")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 正则匹配替换
|
|
|
|
|
+ return str.replace(/&(amp|lt|gt|quot|apos);/g, (match:string, entity:string) => {
|
|
|
|
|
+ const decoded = entityMap.get(`&${entity};`);
|
|
|
|
|
+ return decoded ? decoded : match;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 使用正则表达式提取所有 <D:href> 标签的内容
|
|
|
|
|
+ extractHrefContents(xmlContent: string,rootpath: string,url: string): FileInfo[] {
|
|
|
|
|
+ const filesInfo: FileInfo[] = [];
|
|
|
|
|
+ // 匹配每个 <D:response>
|
|
|
|
|
+ const responseRegex = /<D:response\b[^>]*>([\s\S]*?)<\/D:response>/gi;
|
|
|
|
|
+ let responseMatch: RegExpExecArray | null;
|
|
|
|
|
+
|
|
|
|
|
+ while ((responseMatch = responseRegex.exec(xmlContent)) !== null) {
|
|
|
|
|
+ //console.info(UtilName,'testTag',JSON.stringify(responseMatch))
|
|
|
|
|
+ const responseBlock = responseMatch[1];
|
|
|
|
|
+
|
|
|
|
|
+ // 提取 <D:href> 内容
|
|
|
|
|
+ const hrefMatch = responseBlock.match(/<D:href>(.*?)<\/D:href>/i);
|
|
|
|
|
+ if (!hrefMatch) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 获取完整的href路径
|
|
|
|
|
+ const fullHref = this.decodeXMLEntities(decodeURI(hrefMatch[1]));
|
|
|
|
|
+
|
|
|
|
|
+ // 从href中提取文件名(最后一个/后的部分)
|
|
|
|
|
+ let name = fullHref;
|
|
|
|
|
+ const lastSlashIndex = fullHref.lastIndexOf('/');
|
|
|
|
|
+ if (lastSlashIndex >= 0 && lastSlashIndex < fullHref.length - 1) {
|
|
|
|
|
+ name = fullHref.substring(lastSlashIndex + 1);
|
|
|
|
|
+ } else if (fullHref.endsWith('/')) {
|
|
|
|
|
+ // 如果是目录(以/结尾),取倒数第二段
|
|
|
|
|
+ const withoutTrailingSlash = fullHref.substring(0, fullHref.length - 1);
|
|
|
|
|
+ const secondLastSlash = withoutTrailingSlash.lastIndexOf('/');
|
|
|
|
|
+ if (secondLastSlash >= 0) {
|
|
|
|
|
+ name = withoutTrailingSlash.substring(secondLastSlash + 1) + '/';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 跳过根目录本身
|
|
|
|
|
+ if (name === '' || name === '/') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取<lp1:getcontentlength> 或 <D:getcontentlength>
|
|
|
|
|
+ let sizeMatch = responseBlock.match(/<lp1:getcontentlength>(.*?)<\/lp1:getcontentlength>/i);
|
|
|
|
|
+ if (!sizeMatch) {
|
|
|
|
|
+ sizeMatch = responseBlock.match(/<D:getcontentlength>(.*?)<\/D:getcontentlength>/i);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取<D:getlastmodified> 内容
|
|
|
|
|
+ let lastModifiedMatch = responseBlock.match(/<D:getlastmodified>(.*?)<\/D:getlastmodified>/i);
|
|
|
|
|
+ if(!lastModifiedMatch){
|
|
|
|
|
+ lastModifiedMatch = responseBlock.match(/<lp1:getlastmodified>(.*?)<\/lp1:getlastmodified>/i);
|
|
|
|
|
+ }
|
|
|
|
|
+ const lastModified = lastModifiedMatch ? this.convertToUnixTimestamp(lastModifiedMatch[1]) : 0;
|
|
|
|
|
+ const size = sizeMatch ? Number(sizeMatch[1]) : 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 创建FileInfo并设置WebDAV属性
|
|
|
|
|
+ const fileInfo = new FileInfo(rootpath, name, size, lastModified);
|
|
|
|
|
+ fileInfo.href = fullHref;
|
|
|
|
|
+ fileInfo.contentLength = size;
|
|
|
|
|
+ // 判断是否为文件夹(以/结尾或没有contentLength)
|
|
|
|
|
+ fileInfo.isDirectory = fullHref.endsWith('/') || size === 0;
|
|
|
|
|
+ filesInfo.push(fileInfo);
|
|
|
|
|
+ }
|
|
|
|
|
+ return filesInfo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ console.info(UtilName,'testTag','RcpSocketUtil单例已创建')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取文件列表(简化版包装方法)
|
|
|
|
|
+ public async getFileList(
|
|
|
|
|
+ host: string,
|
|
|
|
|
+ localHost: string,
|
|
|
|
|
+ isUseLocalHost: boolean,
|
|
|
|
|
+ port: number,
|
|
|
|
|
+ path: string,
|
|
|
|
|
+ account: string,
|
|
|
|
|
+ password: string,
|
|
|
|
|
+ enableHttps: boolean
|
|
|
|
|
+ ): Promise<FileInfo[]> {
|
|
|
|
|
+ const actualHost = isUseLocalHost ? localHost : host;
|
|
|
|
|
+ return await this.RcpSendPropFind(actualHost, port, account, password, path, enableHttps);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 订阅HTTP数据传输事件
|
|
|
|
|
+ public subscribeHTTPDataTransfer(callback: (event: string) => void): void {
|
|
|
|
|
+ // 简化版:目前不实现具体订阅逻辑
|
|
|
|
|
+ console.info(UtilName, 'testTag', '订阅HTTP数据传输事件(占位)');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static getInstance(): RcpSocket {
|
|
|
|
|
+ if (!RcpSocket.instance) {
|
|
|
|
|
+ RcpSocket.instance = new RcpSocket();
|
|
|
|
|
+ }
|
|
|
|
|
+ return RcpSocket.instance;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|