|
@@ -15,6 +15,8 @@ import { buffer } from '@kit.ArkTS';
|
|
|
import { CommonConstants } from '../constants/CommonConstants';
|
|
import { CommonConstants } from '../constants/CommonConstants';
|
|
|
import { GlobalContext } from '@pura/harmony-utils';
|
|
import { GlobalContext } from '@pura/harmony-utils';
|
|
|
import { MusicInfo, parseMusicFileName, Utility } from './Utility';
|
|
import { MusicInfo, parseMusicFileName, Utility } from './Utility';
|
|
|
|
|
+import { RemoteDriveType } from '../enums/RemoteDriveType';
|
|
|
|
|
+import { listSmbDirectory, SmbDirectoryEntry } from '../network/SmbBridge';
|
|
|
|
|
|
|
|
const TAG = 'heanup WebdavManager';
|
|
const TAG = 'heanup WebdavManager';
|
|
|
|
|
|
|
@@ -160,6 +162,14 @@ export class WebdavManager {
|
|
|
account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
|
account.coverPath = resultSet.getString(resultSet.getColumnIndex('coverPath'));
|
|
account.coverPath = resultSet.getString(resultSet.getColumnIndex('coverPath'));
|
|
|
account.webType = resultSet.getLong(resultSet.getColumnIndex('webType'));
|
|
account.webType = resultSet.getLong(resultSet.getColumnIndex('webType'));
|
|
|
|
|
+ const smbShareIndex = resultSet.getColumnIndex('smbShare');
|
|
|
|
|
+ const smbDomainIndex = resultSet.getColumnIndex('smbDomain');
|
|
|
|
|
+ if (smbShareIndex >= 0) {
|
|
|
|
|
+ account.smbShare = resultSet.getString(smbShareIndex) ?? '';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (smbDomainIndex >= 0) {
|
|
|
|
|
+ account.smbDomain = resultSet.getString(smbDomainIndex) ?? '';
|
|
|
|
|
+ }
|
|
|
account.isActivate = resultSet.getLong(resultSet.getColumnIndex('isActivate')) === 1;
|
|
account.isActivate = resultSet.getLong(resultSet.getColumnIndex('isActivate')) === 1;
|
|
|
|
|
|
|
|
resultSet.close();
|
|
resultSet.close();
|
|
@@ -290,7 +300,7 @@ export class WebdavManager {
|
|
|
// 查询所有TYPE_WEBDAV且webdav_account_id为空或null的记录
|
|
// 查询所有TYPE_WEBDAV且webdav_account_id为空或null的记录
|
|
|
const querySql = `
|
|
const querySql = `
|
|
|
SELECT id, filePath FROM mediaTable
|
|
SELECT id, filePath FROM mediaTable
|
|
|
- WHERE mtype = ${CommonConstants.TYPE_WEBDAV}
|
|
|
|
|
|
|
+ WHERE mtype IN (${CommonConstants.TYPE_WEBDAV}, ${CommonConstants.TYPE_SMB})
|
|
|
AND (webdav_account_id IS NULL OR webdav_account_id = '')
|
|
AND (webdav_account_id IS NULL OR webdav_account_id = '')
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
@@ -304,7 +314,7 @@ export class WebdavManager {
|
|
|
const updateSql = `
|
|
const updateSql = `
|
|
|
UPDATE mediaTable
|
|
UPDATE mediaTable
|
|
|
SET webdav_account_id = ?
|
|
SET webdav_account_id = ?
|
|
|
- WHERE mtype = ${CommonConstants.TYPE_WEBDAV}
|
|
|
|
|
|
|
+ WHERE mtype IN (${CommonConstants.TYPE_WEBDAV}, ${CommonConstants.TYPE_SMB})
|
|
|
AND (webdav_account_id IS NULL OR webdav_account_id = '')
|
|
AND (webdav_account_id IS NULL OR webdav_account_id = '')
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
@@ -377,6 +387,14 @@ export class WebdavManager {
|
|
|
account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
|
account.coverPath = resultSet.getString(resultSet.getColumnIndex('coverPath'));
|
|
account.coverPath = resultSet.getString(resultSet.getColumnIndex('coverPath'));
|
|
|
account.webType = resultSet.getLong(resultSet.getColumnIndex('webType'));
|
|
account.webType = resultSet.getLong(resultSet.getColumnIndex('webType'));
|
|
|
|
|
+ const smbShareIndex = resultSet.getColumnIndex('smbShare');
|
|
|
|
|
+ const smbDomainIndex = resultSet.getColumnIndex('smbDomain');
|
|
|
|
|
+ if (smbShareIndex >= 0) {
|
|
|
|
|
+ account.smbShare = resultSet.getString(smbShareIndex) ?? '';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (smbDomainIndex >= 0) {
|
|
|
|
|
+ account.smbDomain = resultSet.getString(smbDomainIndex) ?? '';
|
|
|
|
|
+ }
|
|
|
account.isActivate = resultSet.getLong(resultSet.getColumnIndex('isActivate')) === 1;
|
|
account.isActivate = resultSet.getLong(resultSet.getColumnIndex('isActivate')) === 1;
|
|
|
|
|
|
|
|
resultSet.close();
|
|
resultSet.close();
|
|
@@ -410,7 +428,9 @@ export class WebdavManager {
|
|
|
password TEXT,
|
|
password TEXT,
|
|
|
enableHttps INTEGER DEFAULT 0,
|
|
enableHttps INTEGER DEFAULT 0,
|
|
|
coverPath TEXT,
|
|
coverPath TEXT,
|
|
|
- webType INTEGER DEFAULT 0
|
|
|
|
|
|
|
+ webType INTEGER DEFAULT 0,
|
|
|
|
|
+ smbShare TEXT,
|
|
|
|
|
+ smbDomain TEXT
|
|
|
)`;
|
|
)`;
|
|
|
|
|
|
|
|
return this.dataBaseUtil.executeSql(createTableSql)
|
|
return this.dataBaseUtil.executeSql(createTableSql)
|
|
@@ -451,6 +471,24 @@ export class WebdavManager {
|
|
|
// 如果添加字段失败(可能字段已存在),记录但不阻止应用启动
|
|
// 如果添加字段失败(可能字段已存在),记录但不阻止应用启动
|
|
|
Logger.info(TAG, 'webType字段可能已存在或添加失败,继续正常运行');
|
|
Logger.info(TAG, 'webType字段可能已存在或添加失败,继续正常运行');
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Logger.info(TAG, '尝试添加smbShare字段...');
|
|
|
|
|
+ const addSmbShareSql = `ALTER TABLE ${this.webDavTable} ADD COLUMN smbShare TEXT`;
|
|
|
|
|
+ await this.dataBaseUtil.executeSql(addSmbShareSql);
|
|
|
|
|
+ Logger.info(TAG, 'smbShare字段添加成功');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.info(TAG, 'smbShare字段可能已存在或添加失败,继续运行');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Logger.info(TAG, '尝试添加smbDomain字段...');
|
|
|
|
|
+ const addSmbDomainSql = `ALTER TABLE ${this.webDavTable} ADD COLUMN smbDomain TEXT`;
|
|
|
|
|
+ await this.dataBaseUtil.executeSql(addSmbDomainSql);
|
|
|
|
|
+ Logger.info(TAG, 'smbDomain字段添加成功');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Logger.info(TAG, 'smbDomain字段可能已存在或添加失败,继续运行');
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 从数据库查询所有账户
|
|
// 从数据库查询所有账户
|
|
@@ -463,7 +501,7 @@ export class WebdavManager {
|
|
|
// 先查询基础字段(确保这些字段在旧版本中存在)
|
|
// 先查询基础字段(确保这些字段在旧版本中存在)
|
|
|
const baseColumns = ['id', 'name', 'isActivate', 'host', 'localHost', 'isUseLocalHost',
|
|
const baseColumns = ['id', 'name', 'isActivate', 'host', 'localHost', 'isUseLocalHost',
|
|
|
'port', 'filepath', 'imageFilePath', 'lyricFilePath', 'uploadFilePath',
|
|
'port', 'filepath', 'imageFilePath', 'lyricFilePath', 'uploadFilePath',
|
|
|
- 'account', 'password', 'enableHttps'];
|
|
|
|
|
|
|
+ 'account', 'password', 'enableHttps', 'coverPath', 'webType', 'smbShare', 'smbDomain'];
|
|
|
|
|
|
|
|
const resultSet = await this.dataBaseUtil.queryData(this.webDavTable, baseColumns, predicates);
|
|
const resultSet = await this.dataBaseUtil.queryData(this.webDavTable, baseColumns, predicates);
|
|
|
|
|
|
|
@@ -484,42 +522,26 @@ export class WebdavManager {
|
|
|
account.account = resultSet.getString(resultSet.getColumnIndex('account'));
|
|
account.account = resultSet.getString(resultSet.getColumnIndex('account'));
|
|
|
account.password = resultSet.getString(resultSet.getColumnIndex('password'));
|
|
account.password = resultSet.getString(resultSet.getColumnIndex('password'));
|
|
|
account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
|
- // 设置coverPath和webType为默认值undefined,稍后会尝试更新
|
|
|
|
|
- account.coverPath = undefined;
|
|
|
|
|
- account.webType = 0;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const coverPathIndex = resultSet.getColumnIndex('coverPath');
|
|
|
|
|
+ if (coverPathIndex >= 0) {
|
|
|
|
|
+ account.coverPath = resultSet.getString(coverPathIndex) ?? undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+ const webTypeIndex = resultSet.getColumnIndex('webType');
|
|
|
|
|
+ account.webType = webTypeIndex >= 0 ? resultSet.getLong(webTypeIndex) : 0;
|
|
|
|
|
+ const smbShareIndex = resultSet.getColumnIndex('smbShare');
|
|
|
|
|
+ if (smbShareIndex >= 0) {
|
|
|
|
|
+ account.smbShare = resultSet.getString(smbShareIndex) ?? '';
|
|
|
|
|
+ }
|
|
|
|
|
+ const smbDomainIndex = resultSet.getColumnIndex('smbDomain');
|
|
|
|
|
+ if (smbDomainIndex >= 0) {
|
|
|
|
|
+ account.smbDomain = resultSet.getString(smbDomainIndex) ?? '';
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
this.webDavAccounts.push(account);
|
|
this.webDavAccounts.push(account);
|
|
|
}
|
|
}
|
|
|
resultSet.close();
|
|
resultSet.close();
|
|
|
|
|
|
|
|
- // 尝试查询coverPath字段(如果升级成功)
|
|
|
|
|
- try {
|
|
|
|
|
- const coverPathResultSet = await this.dataBaseUtil.queryData(this.webDavTable, ['id', 'coverPath'], predicates);
|
|
|
|
|
- if (coverPathResultSet.goToFirstRow()) {
|
|
|
|
|
- // 创建一个映射来存储coverPath
|
|
|
|
|
- const coverPathMap = new Map<number, string>();
|
|
|
|
|
- do {
|
|
|
|
|
- const accountId = coverPathResultSet.getLong(coverPathResultSet.getColumnIndex('id'));
|
|
|
|
|
- const coverPathIndex = coverPathResultSet.getColumnIndex('coverPath');
|
|
|
|
|
- const coverPath = coverPathIndex >= 0 ? coverPathResultSet.getString(coverPathIndex) : undefined;
|
|
|
|
|
- if (coverPath) {
|
|
|
|
|
- coverPathMap.set(accountId, coverPath);
|
|
|
|
|
- }
|
|
|
|
|
- } while (coverPathResultSet.goToNextRow());
|
|
|
|
|
-
|
|
|
|
|
- // 将coverPath值赋给对应的账户
|
|
|
|
|
- for (const account of this.webDavAccounts) {
|
|
|
|
|
- if (coverPathMap.has(account.id)) {
|
|
|
|
|
- account.coverPath = coverPathMap.get(account.id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- coverPathResultSet.close();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- // 如果查询coverPath失败,说明字段可能不存在,忽略错误
|
|
|
|
|
- Logger.info(TAG, 'coverPath字段不存在或查询失败,使用默认值');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
Logger.info(TAG, '从数据库加载了', this.webDavAccounts.length.toString(), '个WebDAV账户');
|
|
Logger.info(TAG, '从数据库加载了', this.webDavAccounts.length.toString(), '个WebDAV账户');
|
|
|
this.notifyObservers(WebdavManagerStates.QueryAccountsSucceed);
|
|
this.notifyObservers(WebdavManagerStates.QueryAccountsSucceed);
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
@@ -545,7 +567,9 @@ export class WebdavManager {
|
|
|
password: string,
|
|
password: string,
|
|
|
enableHttps: boolean,
|
|
enableHttps: boolean,
|
|
|
coverPath?: string,
|
|
coverPath?: string,
|
|
|
- webType: number = 0
|
|
|
|
|
|
|
+ webType: number = 0,
|
|
|
|
|
+ smbShare: string = '',
|
|
|
|
|
+ smbDomain: string = ''
|
|
|
): Promise<void> {
|
|
): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
const values: relationalStore.ValuesBucket = {
|
|
const values: relationalStore.ValuesBucket = {
|
|
@@ -563,7 +587,9 @@ export class WebdavManager {
|
|
|
'password': password,
|
|
'password': password,
|
|
|
'enableHttps': enableHttps ? 1 : 0,
|
|
'enableHttps': enableHttps ? 1 : 0,
|
|
|
'coverPath': coverPath || null,
|
|
'coverPath': coverPath || null,
|
|
|
- 'webType': webType
|
|
|
|
|
|
|
+ 'webType': webType,
|
|
|
|
|
+ 'smbShare': smbShare,
|
|
|
|
|
+ 'smbDomain': smbDomain
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
await this.dataBaseUtil.insertData(this.webDavTable, values);
|
|
await this.dataBaseUtil.insertData(this.webDavTable, values);
|
|
@@ -597,7 +623,9 @@ export class WebdavManager {
|
|
|
'password': account.password,
|
|
'password': account.password,
|
|
|
'enableHttps': account.enableHttps ? 1 : 0,
|
|
'enableHttps': account.enableHttps ? 1 : 0,
|
|
|
'coverPath': account.coverPath || null,
|
|
'coverPath': account.coverPath || null,
|
|
|
- 'webType': account.webType
|
|
|
|
|
|
|
+ 'webType': account.webType,
|
|
|
|
|
+ 'smbShare': account.smbShare,
|
|
|
|
|
+ 'smbDomain': account.smbDomain
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const predicates = new relationalStore.RdbPredicates(this.webDavTable);
|
|
const predicates = new relationalStore.RdbPredicates(this.webDavTable);
|
|
@@ -657,68 +685,13 @@ export class WebdavManager {
|
|
|
|
|
|
|
|
// 从WebDAV加载文件列表
|
|
// 从WebDAV加载文件列表
|
|
|
public async loadFilesInfoFromWebdav(customPath?: string): Promise<void> {
|
|
public async loadFilesInfoFromWebdav(customPath?: string): Promise<void> {
|
|
|
-
|
|
|
|
|
- const account = this.getActivatedWebDavAccount();
|
|
|
|
|
|
|
+ const account = await this.getActiveWebDavAccount();
|
|
|
if (!account) {
|
|
if (!account) {
|
|
|
Logger.error(TAG, '没有激活的WebDAV账户');
|
|
Logger.error(TAG, '没有激活的WebDAV账户');
|
|
|
this.notifyObservers(WebdavManagerStates.LoadFilesInfoFailed);
|
|
this.notifyObservers(WebdavManagerStates.LoadFilesInfoFailed);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- this.notifyObservers(WebdavManagerStates.LoadFilesInfoStart);
|
|
|
|
|
-
|
|
|
|
|
- // 使用自定义路径或账户默认路径
|
|
|
|
|
- const path = customPath !== undefined ? customPath : account.filepath;
|
|
|
|
|
- this.currentPath = path;
|
|
|
|
|
-
|
|
|
|
|
- const files = await this.rcpSocket.getFileList(
|
|
|
|
|
- account.host,
|
|
|
|
|
- account.localHost,
|
|
|
|
|
- account.isUseLocalHost,
|
|
|
|
|
- account.port,
|
|
|
|
|
- path,
|
|
|
|
|
- account.account,
|
|
|
|
|
- account.password,
|
|
|
|
|
- account.enableHttps
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 保存所有文件(包括文件夹)
|
|
|
|
|
- // 直接使用从RcpSocketUtil返回的FileInfo对象
|
|
|
|
|
- this.webDavFiles = [];
|
|
|
|
|
- for (let i = 0; i < files.length; i++) {
|
|
|
|
|
- const file = files[i];
|
|
|
|
|
- // 直接添加原始文件对象
|
|
|
|
|
- this.webDavFiles.push(file);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 调试:输出获取到的文件总数
|
|
|
|
|
- Logger.info(TAG, '从WebDAV获取到 ' + files.length + ' 个文件/文件夹');
|
|
|
|
|
-
|
|
|
|
|
- // 分别统计文件夹和音频文件
|
|
|
|
|
- let folderCount = 0;
|
|
|
|
|
- let audioCount = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 过滤音频文件
|
|
|
|
|
- this.webDavSongs = [];
|
|
|
|
|
- for (let i = 0; i < files.length; i++) {
|
|
|
|
|
- const file = files[i];
|
|
|
|
|
- const fileName = file.fileName;
|
|
|
|
|
-
|
|
|
|
|
- if (file.isDirectory) {
|
|
|
|
|
- folderCount++;
|
|
|
|
|
- } else if (this.isAudioFile(fileName)) {
|
|
|
|
|
- audioCount++;
|
|
|
|
|
- const song = this.fileInfoToVideoItem(file, account);
|
|
|
|
|
- this.webDavSongs.push(song);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.notifyObservers(WebdavManagerStates.LoadFilesInfoSucceed);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- this.ErrorMessage = error as BusinessError;
|
|
|
|
|
- this.notifyObservers(WebdavManagerStates.LoadFilesInfoFailed);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ await this.loadFilesInfoFromAccount(account, customPath);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -734,49 +707,13 @@ export class WebdavManager {
|
|
|
this.notifyObservers(WebdavManagerStates.LoadFilesInfoStart);
|
|
this.notifyObservers(WebdavManagerStates.LoadFilesInfoStart);
|
|
|
|
|
|
|
|
// 使用自定义路径或账户默认路径
|
|
// 使用自定义路径或账户默认路径
|
|
|
- const path = customPath !== undefined ? customPath : account.filepath;
|
|
|
|
|
- this.currentPath = path;
|
|
|
|
|
-
|
|
|
|
|
- const files = await this.rcpSocket.getFileList(
|
|
|
|
|
- account.host,
|
|
|
|
|
- account.localHost,
|
|
|
|
|
- account.isUseLocalHost,
|
|
|
|
|
- account.port,
|
|
|
|
|
- path,
|
|
|
|
|
- account.account,
|
|
|
|
|
- account.password,
|
|
|
|
|
- account.enableHttps
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 保存所有文件(包括文件夹)
|
|
|
|
|
- // 直接使用从RcpSocketUtil返回的FileInfo对象
|
|
|
|
|
- this.webDavFiles = [];
|
|
|
|
|
- for (let i = 0; i < files.length; i++) {
|
|
|
|
|
- const file = files[i];
|
|
|
|
|
- // 直接添加原始文件对象
|
|
|
|
|
- this.webDavFiles.push(file);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const normalizedFullPath = this.normalizeFullPath(customPath !== undefined ? customPath : account.filepath);
|
|
|
|
|
+ this.currentPath = normalizedFullPath;
|
|
|
|
|
|
|
|
- // 调试:输出获取到的文件总数
|
|
|
|
|
- Logger.info(TAG, '从WebDAV获取到 ' + files.length + ' 个文件/文件夹');
|
|
|
|
|
-
|
|
|
|
|
- // 分别统计文件夹和音频文件
|
|
|
|
|
- let folderCount = 0;
|
|
|
|
|
- let audioCount = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 过滤音频文件
|
|
|
|
|
- this.webDavSongs = [];
|
|
|
|
|
- for (let i = 0; i < files.length; i++) {
|
|
|
|
|
- const file = files[i];
|
|
|
|
|
- const fileName = file.fileName;
|
|
|
|
|
-
|
|
|
|
|
- if (file.isDirectory) {
|
|
|
|
|
- folderCount++;
|
|
|
|
|
- } else if (this.isAudioFile(fileName)) {
|
|
|
|
|
- audioCount++;
|
|
|
|
|
- const song = this.fileInfoToVideoItem(file, account);
|
|
|
|
|
- this.webDavSongs.push(song);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (account.webType === RemoteDriveType.Smb) {
|
|
|
|
|
+ await this.loadSmbFiles(account, normalizedFullPath);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await this.loadWebDavFiles(account, normalizedFullPath);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
this.notifyObservers(WebdavManagerStates.LoadFilesInfoSucceed);
|
|
this.notifyObservers(WebdavManagerStates.LoadFilesInfoSucceed);
|
|
@@ -786,8 +723,86 @@ export class WebdavManager {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private normalizeFullPath(path?: string): string {
|
|
|
|
|
+ if (!path || path.trim().length === 0) {
|
|
|
|
|
+ return '/';
|
|
|
|
|
+ }
|
|
|
|
|
+ let normalized = path.trim().replace(/\\/g, '/');
|
|
|
|
|
+ if (!normalized.startsWith('/')) {
|
|
|
|
|
+ normalized = `/${normalized}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ normalized = normalized.replace(/\/+/g, '/');
|
|
|
|
|
+ if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
|
|
|
+ normalized = normalized.slice(0, -1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return normalized || '/';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private toRelativePath(fullPath: string): string {
|
|
|
|
|
+ if (!fullPath || fullPath === '/') {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+ return fullPath.replace(/^\/+/, '');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async loadWebDavFiles(account: WebDavAccount, fullPath: string): Promise<void> {
|
|
|
|
|
+ const files = await this.rcpSocket.getFileList(
|
|
|
|
|
+ account.host,
|
|
|
|
|
+ account.localHost,
|
|
|
|
|
+ account.isUseLocalHost,
|
|
|
|
|
+ account.port,
|
|
|
|
|
+ fullPath,
|
|
|
|
|
+ account.account,
|
|
|
|
|
+ account.password,
|
|
|
|
|
+ account.enableHttps
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ this.webDavFiles = files;
|
|
|
|
|
+ this.webDavSongs = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
|
|
+ const file = files[i];
|
|
|
|
|
+ if (!file.isDirectory && this.isAudioFile(file.fileName)) {
|
|
|
|
|
+ this.webDavSongs.push(this.fileInfoToVideoItem(file, account));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Logger.info(TAG, `从WebDAV获取到 ${files.length} 个文件/文件夹`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async loadSmbFiles(account: WebDavAccount, fullPath: string): Promise<void> {
|
|
|
|
|
+ if (!account.smbShare) {
|
|
|
|
|
+ throw new Error('SMB账户缺少共享名称');
|
|
|
|
|
+ }
|
|
|
|
|
+ const relativePath = this.toRelativePath(fullPath);
|
|
|
|
|
+ const host = account.isUseLocalHost && account.localHost ? account.localHost : account.host;
|
|
|
|
|
+ const entries = await listSmbDirectory({
|
|
|
|
|
+ host,
|
|
|
|
|
+ share: account.smbShare,
|
|
|
|
|
+ username: account.account,
|
|
|
|
|
+ password: account.password,
|
|
|
|
|
+ domain: account.smbDomain,
|
|
|
|
|
+ path: relativePath
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.webDavFiles = [];
|
|
|
|
|
+ this.webDavSongs = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < entries.length; i++) {
|
|
|
|
|
+ const entry = entries[i];
|
|
|
|
|
+ const fileInfo = this.smbEntryToFileInfo(entry, relativePath);
|
|
|
|
|
+ this.webDavFiles.push(fileInfo);
|
|
|
|
|
+ if (!entry.isDirectory && this.isAudioFile(entry.name)) {
|
|
|
|
|
+ this.webDavSongs.push(this.fileInfoToVideoItem(fileInfo, account));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Logger.info(TAG, `从SMB获取到 ${entries.length} 个文件/文件夹`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 将FileInfo转换为VideoItem
|
|
// 将FileInfo转换为VideoItem
|
|
|
private fileInfoToVideoItem(fileInfo: FileInfo, account: WebDavAccount): VideoItem {
|
|
private fileInfoToVideoItem(fileInfo: FileInfo, account: WebDavAccount): VideoItem {
|
|
|
|
|
+ if (account.webType === RemoteDriveType.Smb) {
|
|
|
|
|
+ return this.buildSmbVideoItem(fileInfo, account);
|
|
|
|
|
+ }
|
|
|
// 构建安全的WebDAV URL(不包含认证信息)
|
|
// 构建安全的WebDAV URL(不包含认证信息)
|
|
|
const protocol = account.enableHttps ? 'https' : 'http';
|
|
const protocol = account.enableHttps ? 'https' : 'http';
|
|
|
const host = account.isUseLocalHost ? account.localHost : account.host;
|
|
const host = account.isUseLocalHost ? account.localHost : account.host;
|
|
@@ -840,6 +855,109 @@ export class WebdavManager {
|
|
|
return videoItem;
|
|
return videoItem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private buildSmbVideoItem(fileInfo: FileInfo, account: WebDavAccount): VideoItem {
|
|
|
|
|
+ const relativePath = fileInfo.href && fileInfo.href.length > 0 ? fileInfo.href : `/${fileInfo.fileName}`;
|
|
|
|
|
+ const normalizedRelative = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;
|
|
|
|
|
+ const sanitizedRelative = this.normalizeSmbRelativePath(normalizedRelative, account.smbShare);
|
|
|
|
|
+ const videoItem = new VideoItem(
|
|
|
|
|
+ this.getFileNameWithoutExtension(fileInfo.fileName),
|
|
|
|
|
+ '',
|
|
|
|
|
+ sanitizedRelative,
|
|
|
|
|
+ CommonConstants.TYPE_SMB,
|
|
|
|
|
+ fileInfo.contentLength,
|
|
|
|
|
+ Utility.getFormatDateStr(fileInfo.time, 'yyyy-MM-dd HH:mm'),
|
|
|
|
|
+ undefined,
|
|
|
|
|
+ undefined,
|
|
|
|
|
+ undefined,
|
|
|
|
|
+ Constants.UNKNOWN_ARTIST,
|
|
|
|
|
+ undefined,
|
|
|
|
|
+ fileInfo.fileName
|
|
|
|
|
+ );
|
|
|
|
|
+ videoItem.size = Utility.formatFSize(fileInfo.contentLength);
|
|
|
|
|
+ if (account && account.id) {
|
|
|
|
|
+ videoItem.webdav_account_id = account.id.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+ videoItem.remote_rel_path = sanitizedRelative;
|
|
|
|
|
+ const hostForDisplay = account.host && account.host.trim().length > 0
|
|
|
|
|
+ ? account.host.trim()
|
|
|
|
|
+ : (account.id?.toString() ?? '');
|
|
|
|
|
+ const cleanedShare = account.smbShare
|
|
|
|
|
+ ? account.smbShare.replace(/^\/+|\/+$/g, '')
|
|
|
|
|
+ : '';
|
|
|
|
|
+ const relativeForDisplay = sanitizedRelative === '/' ? '' : sanitizedRelative;
|
|
|
|
|
+ if (hostForDisplay.length > 0 && cleanedShare.length > 0) {
|
|
|
|
|
+ videoItem.filePath = `smb://${hostForDisplay}/${cleanedShare}${relativeForDisplay}`;
|
|
|
|
|
+ } else if (hostForDisplay.length > 0) {
|
|
|
|
|
+ videoItem.filePath = `smb://${hostForDisplay}${relativeForDisplay}`;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ videoItem.filePath = `smb://${account.id}${relativeForDisplay}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ const musicData: MusicInfo = parseMusicFileName(fileInfo.fileName);
|
|
|
|
|
+ if (musicData.isValid) {
|
|
|
|
|
+ videoItem.artist = musicData.artist;
|
|
|
|
|
+ videoItem.name = musicData.title;
|
|
|
|
|
+ }
|
|
|
|
|
+ return videoItem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private smbEntryToFileInfo(entry: SmbDirectoryEntry, basePath: string): FileInfo {
|
|
|
|
|
+ const normalizedPath = this.combineRemotePath(basePath, entry.name, entry.isDirectory);
|
|
|
|
|
+ const info = new FileInfo('', entry.name, entry.size, Date.now());
|
|
|
|
|
+ info.fileName = entry.name;
|
|
|
|
|
+ info.href = normalizedPath;
|
|
|
|
|
+ info.contentLength = entry.size ?? 0;
|
|
|
|
|
+ info.isDirectory = entry.isDirectory;
|
|
|
|
|
+ return info;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private combineRemotePath(basePath: string, name: string, isDirectory: boolean): string {
|
|
|
|
|
+ const segments: string[] = [];
|
|
|
|
|
+ const trimmedBase = basePath ? basePath.replace(/^\/+/, '').replace(/\/+/g, '/') : '';
|
|
|
|
|
+ if (trimmedBase.length > 0) {
|
|
|
|
|
+ segments.push(trimmedBase);
|
|
|
|
|
+ }
|
|
|
|
|
+ const cleanedName = name.replace(/^\/+/, '').replace(/\/+/g, '/');
|
|
|
|
|
+ if (cleanedName.length > 0) {
|
|
|
|
|
+ segments.push(cleanedName);
|
|
|
|
|
+ }
|
|
|
|
|
+ let fullPath = `/${segments.filter(part => part.length > 0).join('/')}`;
|
|
|
|
|
+ if (isDirectory && !fullPath.endsWith('/')) {
|
|
|
|
|
+ fullPath = `${fullPath}/`;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isDirectory && fullPath.endsWith('/')) {
|
|
|
|
|
+ fullPath = fullPath.slice(0, -1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return fullPath;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private normalizeSmbRelativePath(path: string, shareName?: string): string {
|
|
|
|
|
+ if (!path || path.trim().length === 0) {
|
|
|
|
|
+ return '/';
|
|
|
|
|
+ }
|
|
|
|
|
+ let normalized = path.replace(/\/+/g, '/');
|
|
|
|
|
+ if (!normalized.startsWith('/')) {
|
|
|
|
|
+ normalized = `/${normalized}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
|
|
|
+ normalized = normalized.slice(0, -1);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (shareName) {
|
|
|
|
|
+ const cleanedShare = shareName.replace(/^\/+|\/+$/g, '');
|
|
|
|
|
+ if (cleanedShare.length > 0) {
|
|
|
|
|
+ const prefix = `/${cleanedShare}`;
|
|
|
|
|
+ if (normalized === prefix) {
|
|
|
|
|
+ normalized = '/';
|
|
|
|
|
+ } else if (normalized.startsWith(`${prefix}/`)) {
|
|
|
|
|
+ normalized = normalized.substring(prefix.length);
|
|
|
|
|
+ if (!normalized.startsWith('/')) {
|
|
|
|
|
+ normalized = `/${normalized}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return normalized || '/';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 判断是否为音频文件
|
|
// 判断是否为音频文件
|
|
|
private isAudioFile(fileName: string): boolean {
|
|
private isAudioFile(fileName: string): boolean {
|
|
|
const ext = this.getFileExtension(fileName).toLowerCase();
|
|
const ext = this.getFileExtension(fileName).toLowerCase();
|
|
@@ -885,7 +1003,7 @@ export class WebdavManager {
|
|
|
Logger.info(TAG, '路径历史:', JSON.stringify(this.pathHistory));
|
|
Logger.info(TAG, '路径历史:', JSON.stringify(this.pathHistory));
|
|
|
|
|
|
|
|
// 加载文件夹内容
|
|
// 加载文件夹内容
|
|
|
- await this.loadFilesInfoFromWebdav(folder.href);
|
|
|
|
|
|
|
+ await this.loadFilesInfoFromAccount(this.currentAccount, folder.href);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public async enterFolderFromPath(path: string): Promise<void> {
|
|
public async enterFolderFromPath(path: string): Promise<void> {
|
|
@@ -896,7 +1014,7 @@ export class WebdavManager {
|
|
|
this.pathHistory.push(this.currentPath);
|
|
this.pathHistory.push(this.currentPath);
|
|
|
Logger.info(TAG, '路径历史:', JSON.stringify(this.pathHistory));
|
|
Logger.info(TAG, '路径历史:', JSON.stringify(this.pathHistory));
|
|
|
// 加载文件夹内容
|
|
// 加载文件夹内容
|
|
|
- await this.loadFilesInfoFromWebdav(path);
|
|
|
|
|
|
|
+ await this.loadFilesInfoFromAccount(this.currentAccount, path);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 在 WebdavManager 类中添加以下方法
|
|
// 在 WebdavManager 类中添加以下方法
|
|
@@ -942,7 +1060,7 @@ export class WebdavManager {
|
|
|
// 从历史记录中取出上一级路径
|
|
// 从历史记录中取出上一级路径
|
|
|
const previousPath = this.pathHistory.pop();
|
|
const previousPath = this.pathHistory.pop();
|
|
|
if (previousPath !== undefined) {
|
|
if (previousPath !== undefined) {
|
|
|
- await this.loadFilesInfoFromWebdav(previousPath);
|
|
|
|
|
|
|
+ await this.loadFilesInfoFromAccount(this.currentAccount, previousPath);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|