|
|
@@ -0,0 +1,1160 @@
|
|
|
+// WebdavManager - WebDAV管理器(简化版)
|
|
|
+import { VideoItem } from '../../viewmodel/VideoItem';
|
|
|
+import { common } from '@kit.AbilityKit';
|
|
|
+import { FileInfo } from '../../viewmodel/FileInfo';
|
|
|
+import FileManager, { merge2paths } from './FileManager';
|
|
|
+import { BusinessError } from '@kit.BasicServicesKit';
|
|
|
+import { WebdavManagerStates } from '../enums/WebdavManagerStates';
|
|
|
+import { RcpSocket } from './RcpSocketUtil';
|
|
|
+import { DataBaseUtil } from './DataBaseUtil';
|
|
|
+import { WebDavAccount } from '../../viewmodel/WebDavAccount';
|
|
|
+import { relationalStore } from '@kit.ArkData';
|
|
|
+import { Constants } from '../../Constants';
|
|
|
+import Logger from './Logger';
|
|
|
+import { buffer } from '@kit.ArkTS';
|
|
|
+import { CommonConstants } from '../constants/CommonConstants';
|
|
|
+import { GlobalContext } from '@pura/harmony-utils';
|
|
|
+import { MusicInfo, parseMusicFileName, Utility } from './Utility';
|
|
|
+
|
|
|
+const TAG = 'heanup WebdavManager';
|
|
|
+
|
|
|
+// WebDAV认证信息接口
|
|
|
+export interface WebDavAuthInfo {
|
|
|
+ headers: Record<string, string>;
|
|
|
+ url: string;
|
|
|
+ accountId?: string; // 添加账号ID字段,用于标识认证信息对应的账号
|
|
|
+}
|
|
|
+
|
|
|
+// 流媒体认证信息接口
|
|
|
+export interface StreamAuthInfo {
|
|
|
+ url: string;
|
|
|
+ headers: Record<string, string>;
|
|
|
+}
|
|
|
+
|
|
|
+export interface TransferTask {
|
|
|
+ song: VideoItem;
|
|
|
+ account: WebDavAccount;
|
|
|
+}
|
|
|
+
|
|
|
+@Observed
|
|
|
+export class WebdavManager {
|
|
|
+ public rcpSocket: RcpSocket = RcpSocket.getInstance();
|
|
|
+ private dataBaseUtil = DataBaseUtil.getInstance();
|
|
|
+ public observers: Array<(event: string) => void> = [];
|
|
|
+ public static instance: WebdavManager;
|
|
|
+ public context: common.Context | undefined;
|
|
|
+ public DownloadDirectoryFilePath: string = '';
|
|
|
+ public audioExtensions = Constants.AUDIO_EXTENSIONS;
|
|
|
+ public lyricExtensions = Constants.LYRIC_EXTENSIONS;
|
|
|
+ public imageExtensions = Constants.IMAGE_EXTENSIONS;
|
|
|
+
|
|
|
+ // 数据表名称
|
|
|
+ public preferenceName = Constants.WEBDAV_PREFERENCE_NAME;
|
|
|
+ public webDavTable = 'WebDavAccount';
|
|
|
+
|
|
|
+ public receivedSize: number = 0;
|
|
|
+ public totalSize: number = 0;
|
|
|
+
|
|
|
+ public webDavAccounts: WebDavAccount[] = [];
|
|
|
+ public webDavSongs: VideoItem[] = [];
|
|
|
+ public webDavFiles: FileInfo[] = []; // 当前目录的所有文件(包括文件夹)
|
|
|
+
|
|
|
+ // 路径导航
|
|
|
+ public currentPath: string = ''; // 当前浏览的路径
|
|
|
+ public pathHistory: string[] = []; // 路径历史记录
|
|
|
+
|
|
|
+ // 错误信息
|
|
|
+ public ErrorMessage: string | BusinessError = '';
|
|
|
+
|
|
|
+ // 下载队列
|
|
|
+ public downloadQueue: TransferTask[] = [];
|
|
|
+ public finishDownloadQueue: TransferTask[] = [];
|
|
|
+ public isProcessingQueue: boolean = false;
|
|
|
+ public currentDownloadTask: TransferTask | null = null;
|
|
|
+ public isPauseDownload: boolean = true;
|
|
|
+
|
|
|
+ // 上传队列
|
|
|
+ public uploadQueue: TransferTask[] = [];
|
|
|
+ public finishUploadQueue: TransferTask[] = [];
|
|
|
+ public isProcessingUploadQueue: boolean = false;
|
|
|
+ public currentUploadTask: TransferTask | null = null;
|
|
|
+ public isPauseUpload: boolean = true;
|
|
|
+
|
|
|
+ public currentAccount:WebDavAccount = new WebDavAccount();
|
|
|
+
|
|
|
+ public constructor() {
|
|
|
+ this.rcpSocket.subscribeHTTPDataTransfer((event: string) => {
|
|
|
+ this.notifyObservers(event);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public static getInstance(): WebdavManager {
|
|
|
+ if (!WebdavManager.instance) {
|
|
|
+ WebdavManager.instance = new WebdavManager();
|
|
|
+ }
|
|
|
+ return WebdavManager.instance;
|
|
|
+ }
|
|
|
+
|
|
|
+ public setContext(context: common.Context): void {
|
|
|
+ this.context = context;
|
|
|
+ this.DownloadDirectoryFilePath = merge2paths(context.filesDir, 'Download');
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 观察者模式 ====================
|
|
|
+
|
|
|
+ public subscribe(callback: (event: string) => void): void {
|
|
|
+ this.observers.push(callback);
|
|
|
+ }
|
|
|
+
|
|
|
+ public unsubscribe(callback: (event: string) => void): void {
|
|
|
+ const index = this.observers.indexOf(callback);
|
|
|
+ if (index > -1) {
|
|
|
+ this.observers.splice(index, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public notifyObservers(event: string): void {
|
|
|
+ Logger.info(TAG, '通知观察者:', event);
|
|
|
+ Logger.info(TAG, '观察者数量:', this.observers.length.toString());
|
|
|
+ for (let i = 0; i < this.observers.length; i++) {
|
|
|
+ const observer = this.observers[i];
|
|
|
+ Logger.info(TAG, '调用观察者', i.toString(), ',事件:', event);
|
|
|
+ observer(event);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 数据库操作 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据webdav_account_id查询WebDAV账号信息
|
|
|
+ * @param accountId WebDAV账号ID
|
|
|
+ * @returns Promise<WebDavAccount | null> 返回WebDAV账号信息或null
|
|
|
+ */
|
|
|
+ public async getWebDavAccountById(accountId: string): Promise<WebDavAccount | null> {
|
|
|
+ if (!accountId) {
|
|
|
+ Logger.warn(TAG, 'webdav_account_id为空,无法查询账号信息');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const db = await this.dataBaseUtil.getDB();
|
|
|
+ const predicates = new relationalStore.RdbPredicates(this.webDavTable);
|
|
|
+ predicates.equalTo('id', accountId);
|
|
|
+
|
|
|
+ const resultSet = await db.query(predicates);
|
|
|
+ if (resultSet.rowCount > 0) {
|
|
|
+ resultSet.goToFirstRow();
|
|
|
+ const account = new WebDavAccount();
|
|
|
+ account.id = resultSet.getLong(resultSet.getColumnIndex('id'));
|
|
|
+ account.name = resultSet.getString(resultSet.getColumnIndex('name'));
|
|
|
+ account.host = resultSet.getString(resultSet.getColumnIndex('host'));
|
|
|
+ account.localHost = resultSet.getString(resultSet.getColumnIndex('localHost'));
|
|
|
+ account.isUseLocalHost = resultSet.getLong(resultSet.getColumnIndex('isUseLocalHost')) === 1;
|
|
|
+ account.port = resultSet.getLong(resultSet.getColumnIndex('port'));
|
|
|
+ account.filepath = resultSet.getString(resultSet.getColumnIndex('filepath'));
|
|
|
+ account.imageFilePath = resultSet.getString(resultSet.getColumnIndex('imageFilePath'));
|
|
|
+ account.lyricFilePath = resultSet.getString(resultSet.getColumnIndex('lyricFilePath'));
|
|
|
+ account.uploadFilePath = resultSet.getString(resultSet.getColumnIndex('uploadFilePath'));
|
|
|
+ account.account = resultSet.getString(resultSet.getColumnIndex('account'));
|
|
|
+ account.password = resultSet.getString(resultSet.getColumnIndex('password'));
|
|
|
+ account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
|
+ account.coverPath = resultSet.getString(resultSet.getColumnIndex('coverPath'));
|
|
|
+ account.isActivate = resultSet.getLong(resultSet.getColumnIndex('isActivate')) === 1;
|
|
|
+
|
|
|
+ resultSet.close();
|
|
|
+ Logger.info(TAG, `成功查询到WebDAV账号: ${account.name}, ID: ${account.id}`);
|
|
|
+ return account;
|
|
|
+ } else {
|
|
|
+ Logger.warn(TAG, `未找到ID为 ${accountId} 的WebDAV账号`);
|
|
|
+ resultSet.close();
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ const err = error as Error;
|
|
|
+ Logger.error(TAG, `查询WebDAV账号失败: ${err.message}`);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据WebDAV账号构建认证信息
|
|
|
+ * @param accountId WebDAV账号ID
|
|
|
+ * @returns Promise<WebDavAuthInfo | null> 返回认证信息或null
|
|
|
+ */
|
|
|
+ public async buildAuthInfoByAccountId(accountId: string): Promise<WebDavAuthInfo | null> {
|
|
|
+ const account = await this.getWebDavAccountById(accountId);
|
|
|
+ if (!account) {
|
|
|
+ Logger.warn(TAG, `无法构建认证信息,账号ID ${accountId} 对应的账号不存在`);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 构建基础URL
|
|
|
+ const protocol = account.enableHttps ? 'https' : 'http';
|
|
|
+ const host = account.isUseLocalHost && account.localHost ? account.localHost : account.host;
|
|
|
+ const port = account.port && account.port !== 80 && account.port !== 443 ? `:${account.port}` : '';
|
|
|
+ const baseUrl = `${protocol}://${host}${port}`;
|
|
|
+
|
|
|
+ // 构建认证头
|
|
|
+ const headers: Record<string, string> = {};
|
|
|
+ if (account.account && account.password) {
|
|
|
+ const credentials = `${account.account}:${account.password}`;
|
|
|
+ // 简化base64编码实现,避免复杂的类型转换
|
|
|
+ try {
|
|
|
+ const bufferObj = buffer.from(credentials, 'utf-8');
|
|
|
+ const base64Credentials = bufferObj.toString('base64');
|
|
|
+ headers['Authorization'] = `Basic ${base64Credentials}`;
|
|
|
+ } catch (error) {
|
|
|
+ const err = error as Error;
|
|
|
+ Logger.warn(TAG, `base64编码失败,使用原始凭据: ${err.message}`);
|
|
|
+ headers['Authorization'] = `Basic ${credentials}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Logger.info(TAG, `成功构建WebDAV认证信息,账号: ${account.name}, URL: ${baseUrl}`);
|
|
|
+
|
|
|
+ return {
|
|
|
+ headers: headers,
|
|
|
+ url: baseUrl,
|
|
|
+ accountId: accountId
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ const err = error as Error;
|
|
|
+ Logger.error(TAG, `构建WebDAV认证信息失败: ${err.message}`);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建HTTP请求头,通过webdav_account_id查询认证信息
|
|
|
+ * @param currentSong - 当前播放的歌曲信息
|
|
|
+ * @param videoUrl - 当前歌曲的URL
|
|
|
+ * @returns Promise<Map<string, string>> HTTP请求头
|
|
|
+ */
|
|
|
+ public async buildHttpHeadersWithAccountId(
|
|
|
+ currentSong: VideoItem | undefined,
|
|
|
+ videoUrl: string
|
|
|
+ ): Promise<Map<string, string>> {
|
|
|
+ const headers = new Map<string, string>();
|
|
|
+
|
|
|
+ if (!currentSong) {
|
|
|
+ return headers;
|
|
|
+ }
|
|
|
+
|
|
|
+ Logger.info(`heanup 当前歌曲信息 - name: ${currentSong.name}, type: ${currentSong.type}, filePath: ${currentSong.filePath}`);
|
|
|
+
|
|
|
+ // 检查是否为WebDAV歌曲
|
|
|
+ if (currentSong.type === CommonConstants.TYPE_WEBDAV) {
|
|
|
+ // 优先使用webdav_account_id查询认证信息
|
|
|
+ if (currentSong.webdav_account_id) {
|
|
|
+ Logger.info(`heanup 使用webdav_account_id查询认证信息,账号ID: ${currentSong.webdav_account_id}`);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const authInfo = await this.buildAuthInfoByAccountId(currentSong.webdav_account_id);
|
|
|
+ if (authInfo) {
|
|
|
+ // 添加WebDAV认证头
|
|
|
+ Object.keys(authInfo.headers).forEach(key => {
|
|
|
+ headers.set(key, authInfo.headers[key]);
|
|
|
+ });
|
|
|
+ Logger.info(`heanup 成功通过webdav_account_id获取WebDAV认证信息`);
|
|
|
+ } else {
|
|
|
+ Logger.warn(`heanup 无法通过webdav_account_id ${currentSong.webdav_account_id} 获取认证信息`);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ const err = error as Error;
|
|
|
+ Logger.error(`heanup 查询webdav_account_id认证信息时出错: ${err.message}`);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Logger.warn(`heanup WebDAV歌曲缺少webdav_account_id字段,无法查询认证信息`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有认证信息,记录警告
|
|
|
+ if (headers.size === 0) {
|
|
|
+ Logger.warn(`heanup WebDAV歌曲缺少认证信息,可能需要手动配置WebDAV账户`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return headers;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新现有WebDAV歌曲的webdav_account_id字段
|
|
|
+ * 用于修复旧版本数据库中缺少webdav_account_id的记录
|
|
|
+ * @param accountId WebDAV账号ID
|
|
|
+ */
|
|
|
+ public async updateWebDavSongsAccountId(accountId: string): Promise<void> {
|
|
|
+ try {
|
|
|
+ const db = await this.dataBaseUtil.getDB();
|
|
|
+
|
|
|
+ // 查询所有TYPE_WEBDAV且webdav_account_id为空或null的记录
|
|
|
+ const querySql = `
|
|
|
+ SELECT id, filePath FROM mediaTable
|
|
|
+ WHERE mtype = ${CommonConstants.TYPE_WEBDAV}
|
|
|
+ AND (webdav_account_id IS NULL OR webdav_account_id = '')
|
|
|
+ `;
|
|
|
+
|
|
|
+ const resultSet = await db.querySql(querySql);
|
|
|
+ const updateCount = resultSet.rowCount;
|
|
|
+
|
|
|
+ if (updateCount > 0) {
|
|
|
+ Logger.info(TAG, `发现 ${updateCount} 个WebDAV歌曲缺少webdav_account_id,开始更新`);
|
|
|
+
|
|
|
+ // 更新这些记录的webdav_account_id
|
|
|
+ const updateSql = `
|
|
|
+ UPDATE mediaTable
|
|
|
+ SET webdav_account_id = ?
|
|
|
+ WHERE mtype = ${CommonConstants.TYPE_WEBDAV}
|
|
|
+ AND (webdav_account_id IS NULL OR webdav_account_id = '')
|
|
|
+ `;
|
|
|
+
|
|
|
+ await db.executeSql(updateSql, [accountId]);
|
|
|
+ Logger.info(TAG, `成功更新 ${updateCount} 个WebDAV歌曲的webdav_account_id为: ${accountId}`);
|
|
|
+ } else {
|
|
|
+ Logger.info(TAG, `所有WebDAV歌曲都已有webdav_account_id,无需更新`);
|
|
|
+ }
|
|
|
+
|
|
|
+ resultSet.close();
|
|
|
+ } catch (error) {
|
|
|
+ const err = error as Error;
|
|
|
+ Logger.error(TAG, `更新WebDAV歌曲webdav_account_id失败: ${err.message}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 修复所有WebDAV歌曲的webdav_account_id字段
|
|
|
+ * 用于全局修复旧版本数据库中缺少webdav_account_id的记录
|
|
|
+ * @param accountId WebDAV账号ID(可选,如果不提供则使用当前激活的账号)
|
|
|
+ */
|
|
|
+ public async fixAllWebDavSongsAccountId(accountId?: string): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 如果没有提供accountId,尝试使用当前激活的账号
|
|
|
+ if (!accountId) {
|
|
|
+ const activeAccount = await this.getActiveWebDavAccount();
|
|
|
+ if (activeAccount && activeAccount.id) {
|
|
|
+ accountId = activeAccount.id.toString();
|
|
|
+ } else {
|
|
|
+ Logger.warn(TAG, `无法找到激活的WebDAV账号来修复歌曲记录`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Logger.info(TAG, `开始修复所有WebDAV歌曲的webdav_account_id,使用账号ID: ${accountId}`);
|
|
|
+ await this.updateWebDavSongsAccountId(accountId);
|
|
|
+ Logger.info(TAG, `WebDAV歌曲webdav_account_id修复完成`);
|
|
|
+ } catch (error) {
|
|
|
+ const err = error as Error;
|
|
|
+ Logger.error(TAG, `修复WebDAV歌曲webdav_account_id失败: ${err.message}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前激活的WebDAV账号
|
|
|
+ */
|
|
|
+ private async getActiveWebDavAccount(): Promise<WebDavAccount | null> {
|
|
|
+ try {
|
|
|
+ const db = await this.dataBaseUtil.getDB();
|
|
|
+ const predicates = new relationalStore.RdbPredicates(this.webDavTable);
|
|
|
+ predicates.equalTo('isActivate', 1);
|
|
|
+ predicates.limitAs(1);
|
|
|
+
|
|
|
+ const resultSet = await db.query(predicates);
|
|
|
+ if (resultSet.rowCount > 0) {
|
|
|
+ resultSet.goToFirstRow();
|
|
|
+ const account = new WebDavAccount();
|
|
|
+ account.id = resultSet.getLong(resultSet.getColumnIndex('id'));
|
|
|
+ account.name = resultSet.getString(resultSet.getColumnIndex('name'));
|
|
|
+ account.host = resultSet.getString(resultSet.getColumnIndex('host'));
|
|
|
+ account.localHost = resultSet.getString(resultSet.getColumnIndex('localHost'));
|
|
|
+ account.isUseLocalHost = resultSet.getLong(resultSet.getColumnIndex('isUseLocalHost')) === 1;
|
|
|
+ account.port = resultSet.getLong(resultSet.getColumnIndex('port'));
|
|
|
+ account.filepath = resultSet.getString(resultSet.getColumnIndex('filepath'));
|
|
|
+ account.imageFilePath = resultSet.getString(resultSet.getColumnIndex('imageFilePath'));
|
|
|
+ account.lyricFilePath = resultSet.getString(resultSet.getColumnIndex('lyricFilePath'));
|
|
|
+ account.uploadFilePath = resultSet.getString(resultSet.getColumnIndex('uploadFilePath'));
|
|
|
+ account.account = resultSet.getString(resultSet.getColumnIndex('account'));
|
|
|
+ account.password = resultSet.getString(resultSet.getColumnIndex('password'));
|
|
|
+ account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
|
+ account.coverPath = resultSet.getString(resultSet.getColumnIndex('coverPath'));
|
|
|
+ account.isActivate = resultSet.getLong(resultSet.getColumnIndex('isActivate')) === 1;
|
|
|
+
|
|
|
+ resultSet.close();
|
|
|
+ return account;
|
|
|
+ } else {
|
|
|
+ resultSet.close();
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ const err = error as Error;
|
|
|
+ Logger.error(TAG, `获取激活的WebDAV账号失败: ${err.message}`);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建WebDAV账户表
|
|
|
+ public createWebDavTableInDB(): Promise<void> {
|
|
|
+ const createTableSql = `CREATE TABLE IF NOT EXISTS ${this.webDavTable} (
|
|
|
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
+ name TEXT,
|
|
|
+ isActivate INTEGER DEFAULT 1,
|
|
|
+ host TEXT,
|
|
|
+ localHost TEXT,
|
|
|
+ isUseLocalHost INTEGER DEFAULT 0,
|
|
|
+ port INTEGER,
|
|
|
+ filepath TEXT,
|
|
|
+ imageFilePath TEXT,
|
|
|
+ lyricFilePath TEXT,
|
|
|
+ uploadFilePath TEXT,
|
|
|
+ account TEXT,
|
|
|
+ password TEXT,
|
|
|
+ enableHttps INTEGER DEFAULT 0,
|
|
|
+ coverPath TEXT
|
|
|
+ )`;
|
|
|
+
|
|
|
+ return this.dataBaseUtil.executeSql(createTableSql)
|
|
|
+ .then(() => {
|
|
|
+ Logger.info(TAG, 'WebDAV账户表创建成功');
|
|
|
+
|
|
|
+ // 检查并添加新字段(用于数据库升级)
|
|
|
+ return this.upgradeWebDavTable();
|
|
|
+ })
|
|
|
+ .catch((err: Error) => {
|
|
|
+ Logger.error(TAG, '创建WebDAV账户表失败:', err.message);
|
|
|
+ throw err;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 升级WebDAV表结构
|
|
|
+ private async upgradeWebDavTable(): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 直接尝试添加coverPath字段,如果字段已存在会失败但不影响应用运行
|
|
|
+ Logger.info(TAG, '检查数据库表结构,尝试添加coverPath字段...');
|
|
|
+ const addColumnSql = `ALTER TABLE ${this.webDavTable} ADD COLUMN coverPath TEXT`;
|
|
|
+
|
|
|
+ await this.dataBaseUtil.executeSql(addColumnSql);
|
|
|
+ Logger.info(TAG, 'coverPath字段添加成功,数据库升级完成');
|
|
|
+ } catch (error) {
|
|
|
+ // 如果添加字段失败(可能字段已存在),记录但不阻止应用启动
|
|
|
+ Logger.info(TAG, 'coverPath字段可能已存在或添加失败,继续正常运行');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从数据库查询所有账户
|
|
|
+ public async queryWebDavAccountsFromDB(): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 确保表结构是最新的
|
|
|
+ await this.upgradeWebDavTable();
|
|
|
+
|
|
|
+ const predicates = new relationalStore.RdbPredicates(this.webDavTable);
|
|
|
+ // 先查询基础字段(确保这些字段在旧版本中存在)
|
|
|
+ const baseColumns = ['id', 'name', 'isActivate', 'host', 'localHost', 'isUseLocalHost',
|
|
|
+ 'port', 'filepath', 'imageFilePath', 'lyricFilePath', 'uploadFilePath',
|
|
|
+ 'account', 'password', 'enableHttps'];
|
|
|
+
|
|
|
+ const resultSet = await this.dataBaseUtil.queryData(this.webDavTable, baseColumns, predicates);
|
|
|
+
|
|
|
+ this.webDavAccounts = [];
|
|
|
+ while (resultSet.goToNextRow()) {
|
|
|
+ const account = new WebDavAccount();
|
|
|
+ account.id = resultSet.getLong(resultSet.getColumnIndex('id'));
|
|
|
+ account.name = resultSet.getString(resultSet.getColumnIndex('name'));
|
|
|
+ account.isActivate = resultSet.getLong(resultSet.getColumnIndex('isActivate')) === 1;
|
|
|
+ account.host = resultSet.getString(resultSet.getColumnIndex('host'));
|
|
|
+ account.localHost = resultSet.getString(resultSet.getColumnIndex('localHost'));
|
|
|
+ account.isUseLocalHost = resultSet.getLong(resultSet.getColumnIndex('isUseLocalHost')) === 1;
|
|
|
+ account.port = resultSet.getLong(resultSet.getColumnIndex('port'));
|
|
|
+ account.filepath = resultSet.getString(resultSet.getColumnIndex('filepath'));
|
|
|
+ account.imageFilePath = resultSet.getString(resultSet.getColumnIndex('imageFilePath'));
|
|
|
+ account.lyricFilePath = resultSet.getString(resultSet.getColumnIndex('lyricFilePath'));
|
|
|
+ account.uploadFilePath = resultSet.getString(resultSet.getColumnIndex('uploadFilePath'));
|
|
|
+ account.account = resultSet.getString(resultSet.getColumnIndex('account'));
|
|
|
+ account.password = resultSet.getString(resultSet.getColumnIndex('password'));
|
|
|
+ account.enableHttps = resultSet.getLong(resultSet.getColumnIndex('enableHttps')) === 1;
|
|
|
+ // 设置coverPath为默认值undefined,稍后会尝试更新
|
|
|
+ account.coverPath = undefined;
|
|
|
+
|
|
|
+ this.webDavAccounts.push(account);
|
|
|
+ }
|
|
|
+ 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账户');
|
|
|
+ this.notifyObservers(WebdavManagerStates.QueryAccountsSucceed);
|
|
|
+ } catch (err) {
|
|
|
+ const error = err as Error;
|
|
|
+ Logger.error(TAG, '查询WebDAV账户失败:', error.message);
|
|
|
+ this.notifyObservers(WebdavManagerStates.QueryAccountsFailed);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入新账户
|
|
|
+ public async insertAccount(
|
|
|
+ name: string,
|
|
|
+ host: string,
|
|
|
+ localHost: string,
|
|
|
+ isUseLocalHost: boolean,
|
|
|
+ port: number,
|
|
|
+ filepath: string,
|
|
|
+ lyricFilePath: string,
|
|
|
+ uploadFilePath: string,
|
|
|
+ imageFilePath: string,
|
|
|
+ account: string,
|
|
|
+ password: string,
|
|
|
+ enableHttps: boolean,
|
|
|
+ coverPath?: string
|
|
|
+ ): Promise<void> {
|
|
|
+ try {
|
|
|
+ const values: relationalStore.ValuesBucket = {
|
|
|
+ 'name': name,
|
|
|
+ 'isActivate': 1,
|
|
|
+ 'host': host,
|
|
|
+ 'localHost': localHost,
|
|
|
+ 'isUseLocalHost': isUseLocalHost ? 1 : 0,
|
|
|
+ 'port': port,
|
|
|
+ 'filepath': filepath,
|
|
|
+ 'imageFilePath': imageFilePath,
|
|
|
+ 'lyricFilePath': lyricFilePath,
|
|
|
+ 'uploadFilePath': uploadFilePath,
|
|
|
+ 'account': account,
|
|
|
+ 'password': password,
|
|
|
+ 'enableHttps': enableHttps ? 1 : 0,
|
|
|
+ 'coverPath': coverPath || null
|
|
|
+ };
|
|
|
+
|
|
|
+ await this.dataBaseUtil.insertData(this.webDavTable, values);
|
|
|
+ Logger.info(TAG, '插入WebDAV账户成功:', name);
|
|
|
+
|
|
|
+ await this.queryWebDavAccountsFromDB();
|
|
|
+ this.notifyObservers(WebdavManagerStates.InsertAccountSucceed);
|
|
|
+ } catch (err) {
|
|
|
+ const error = err as Error;
|
|
|
+ Logger.error(TAG, '插入WebDAV账户失败:', error.message);
|
|
|
+ this.notifyObservers(WebdavManagerStates.InsertAccountFailed);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 编辑账户
|
|
|
+ public async editAccount(account: WebDavAccount): Promise<void> {
|
|
|
+ try {
|
|
|
+ const values: relationalStore.ValuesBucket = {
|
|
|
+ 'name': account.name,
|
|
|
+ 'isActivate': account.isActivate ? 1 : 0,
|
|
|
+ 'host': account.host,
|
|
|
+ 'localHost': account.localHost,
|
|
|
+ 'isUseLocalHost': account.isUseLocalHost ? 1 : 0,
|
|
|
+ 'port': account.port,
|
|
|
+ 'filepath': account.filepath,
|
|
|
+ 'imageFilePath': account.imageFilePath,
|
|
|
+ 'lyricFilePath': account.lyricFilePath,
|
|
|
+ 'uploadFilePath': account.uploadFilePath,
|
|
|
+ 'account': account.account,
|
|
|
+ 'password': account.password,
|
|
|
+ 'enableHttps': account.enableHttps ? 1 : 0,
|
|
|
+ 'coverPath': account.coverPath || null
|
|
|
+ };
|
|
|
+
|
|
|
+ const predicates = new relationalStore.RdbPredicates(this.webDavTable);
|
|
|
+ predicates.equalTo('id', account.id);
|
|
|
+
|
|
|
+ await this.dataBaseUtil.updateData(this.webDavTable, values, predicates);
|
|
|
+ Logger.info(TAG, '更新WebDAV账户成功:', account.name);
|
|
|
+
|
|
|
+ await this.queryWebDavAccountsFromDB();
|
|
|
+ this.notifyObservers(WebdavManagerStates.EditAccountSucceed);
|
|
|
+ } catch (err) {
|
|
|
+ const error = err as Error;
|
|
|
+ Logger.error(TAG, '更新WebDAV账户失败:', error.message);
|
|
|
+ this.notifyObservers(WebdavManagerStates.EditAccountFailed);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除账户
|
|
|
+ public async removeAccount(account: WebDavAccount): Promise<void> {
|
|
|
+ try {
|
|
|
+ const predicates = new relationalStore.RdbPredicates(this.webDavTable);
|
|
|
+ predicates.equalTo('id', account.id);
|
|
|
+
|
|
|
+ await this.dataBaseUtil.deleteData(predicates);
|
|
|
+ Logger.info(TAG, '删除WebDAV账户成功:', account.name);
|
|
|
+
|
|
|
+ await this.queryWebDavAccountsFromDB();
|
|
|
+ this.notifyObservers(WebdavManagerStates.RemoveAccountSucceed);
|
|
|
+ } catch (err) {
|
|
|
+ const error = err as Error;
|
|
|
+ Logger.error(TAG, '删除WebDAV账户失败:', error.message);
|
|
|
+ this.notifyObservers(WebdavManagerStates.RemoveAccountFailed);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有账户
|
|
|
+ public getAllWebDavAccounts(): WebDavAccount[] {
|
|
|
+ return this.webDavAccounts;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取激活的账户
|
|
|
+ public getActivatedWebDavAccount(): WebDavAccount | null {
|
|
|
+ if(this.currentAccount)
|
|
|
+ return this.currentAccount;
|
|
|
+ for (let i = 0; i < this.webDavAccounts.length; i++) {
|
|
|
+ const account = this.webDavAccounts[i];
|
|
|
+ if (account.isActivate) {
|
|
|
+ return account;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 文件操作 ====================
|
|
|
+
|
|
|
+ // 从WebDAV加载文件列表
|
|
|
+ public async loadFilesInfoFromWebdav(customPath?: string): Promise<void> {
|
|
|
+
|
|
|
+ const account = this.getActivatedWebDavAccount();
|
|
|
+ if (!account) {
|
|
|
+ Logger.error(TAG, '没有激活的WebDAV账户');
|
|
|
+ this.notifyObservers(WebdavManagerStates.LoadFilesInfoFailed);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public async loadFilesInfoFromAccount(account: WebDavAccount,customPath?: string): Promise<void> {
|
|
|
+ this.currentAccount = account;
|
|
|
+
|
|
|
+ // 更新现有WebDAV歌曲的webdav_account_id字段
|
|
|
+ if (account && account.id) {
|
|
|
+ await this.updateWebDavSongsAccountId(account.id.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将FileInfo转换为VideoItem
|
|
|
+ private fileInfoToVideoItem(fileInfo: FileInfo, account: WebDavAccount): VideoItem {
|
|
|
+ // 构建安全的WebDAV URL(不包含认证信息)
|
|
|
+ const protocol = account.enableHttps ? 'https' : 'http';
|
|
|
+ const host = account.isUseLocalHost ? account.localHost : account.host;
|
|
|
+ const port = account.port;
|
|
|
+
|
|
|
+ // 构建基础URL,确保路径重新编码(目录/文件名中的中文与空格)
|
|
|
+ const encodedHref = encodeURI(fileInfo.href); // 仅编码非保留字符,已编码的百分号不重复编码
|
|
|
+ const filePath = `${protocol}://${host}:${port}${encodedHref}`;
|
|
|
+ Logger.info(TAG, '编码后的WebDAV URL: ' + filePath);
|
|
|
+
|
|
|
+ // 创建VideoItem对象
|
|
|
+ // 构造函数签名: (name, id, filePath, type, videoSize, cTime, pixelMap?, size?, pixelMapPath?, artist?, album?, fileName?, lastPlayed?)
|
|
|
+
|
|
|
+ const videoItem = new VideoItem(
|
|
|
+ this.getFileNameWithoutExtension(fileInfo.fileName), // name: 歌曲名
|
|
|
+ '', // id: 空字符串,WebDAV文件无本地ID
|
|
|
+ filePath, // filePath: 文件路径
|
|
|
+ CommonConstants.TYPE_WEBDAV, // type: WebDAV类型
|
|
|
+ fileInfo.contentLength, // videoSize: 文件大小
|
|
|
+ Utility.getFormatDateStr(fileInfo.time,'yyyy-MM-dd HH:mm'), // cTime: 修改时间
|
|
|
+ undefined, // pixelMap
|
|
|
+ undefined, // size
|
|
|
+ undefined, // pixelMapPath: 对于WebDAV歌曲不设置图片路径
|
|
|
+ Constants.UNKNOWN_ARTIST, // artist: 艺术家
|
|
|
+ undefined, // album: 专辑
|
|
|
+ fileInfo.fileName // fileName: 真实文件名
|
|
|
+ );
|
|
|
+ videoItem.size = Utility.formatFSize(fileInfo.contentLength)
|
|
|
+ //根据文件名解析艺术家
|
|
|
+ const musicData:MusicInfo = parseMusicFileName(fileInfo.fileName);
|
|
|
+ if (musicData.isValid) {
|
|
|
+ console.log('onecold parseMusicFileName artist:', musicData.artist)
|
|
|
+ videoItem.artist = musicData.artist
|
|
|
+ videoItem.name = musicData.title
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置WebDAV账号ID,用于后续认证信息查询
|
|
|
+ if (account && account.id) {
|
|
|
+ videoItem.webdav_account_id = account.id.toString();
|
|
|
+ Logger.info(TAG, `设置WebDAV歌曲 ${videoItem.name} 的账号ID: ${videoItem.webdav_account_id}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return videoItem;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否为音频文件
|
|
|
+ private isAudioFile(fileName: string): boolean {
|
|
|
+ const ext = this.getFileExtension(fileName).toLowerCase();
|
|
|
+ for (let i = 0; i < this.audioExtensions.length; i++) {
|
|
|
+ if (ext === this.audioExtensions[i]) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取文件扩展名
|
|
|
+ private getFileExtension(fileName: string): string {
|
|
|
+ const lastDotIndex = fileName.lastIndexOf('.');
|
|
|
+ if (lastDotIndex === -1) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ return fileName.substring(lastDotIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取不带扩展名的文件名
|
|
|
+ private getFileNameWithoutExtension(fileName: string): string {
|
|
|
+ const lastDotIndex = fileName.lastIndexOf('.');
|
|
|
+ if (lastDotIndex === -1) {
|
|
|
+ return fileName;
|
|
|
+ }
|
|
|
+ return fileName.substring(0, lastDotIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 进入文件夹
|
|
|
+ public async enterFolder(folder: FileInfo): Promise<void> {
|
|
|
+ if (!folder.isDirectory) {
|
|
|
+ Logger.error(TAG, '不是文件夹,无法进入');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Logger.info(TAG, '准备进入文件夹:', folder.fileName);
|
|
|
+ Logger.info(TAG, '当前路径:', this.currentPath);
|
|
|
+ Logger.info(TAG, '目标路径:', folder.href);
|
|
|
+
|
|
|
+ // 保存当前路径到历史记录
|
|
|
+ this.pathHistory.push(this.currentPath);
|
|
|
+ Logger.info(TAG, '路径历史:', JSON.stringify(this.pathHistory));
|
|
|
+
|
|
|
+ // 加载文件夹内容
|
|
|
+ await this.loadFilesInfoFromWebdav(folder.href);
|
|
|
+ }
|
|
|
+
|
|
|
+ public async enterFolderFromPath(path: string): Promise<void> {
|
|
|
+
|
|
|
+ Logger.info(TAG, '当前路径:', this.currentPath);
|
|
|
+ Logger.info(TAG, '目标路径:', path);
|
|
|
+ // 保存当前路径到历史记录
|
|
|
+ this.pathHistory.push(this.currentPath);
|
|
|
+ Logger.info(TAG, '路径历史:', JSON.stringify(this.pathHistory));
|
|
|
+ // 加载文件夹内容
|
|
|
+ await this.loadFilesInfoFromWebdav(path);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在 WebdavManager 类中添加以下方法
|
|
|
+ navigateToBreadcrumb(breadcrumbIndex: number): Promise<string> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ try {
|
|
|
+ // 面包屑索引0是"根目录"
|
|
|
+ if (breadcrumbIndex === 0) {
|
|
|
+ resolve('/');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据当前路径构建目标路径
|
|
|
+ const pathParts = this.currentPath.split('/').filter(part => part !== '');
|
|
|
+
|
|
|
+ // 验证索引有效性
|
|
|
+ if (breadcrumbIndex > pathParts.length) {
|
|
|
+ reject(new Error('Invalid breadcrumb index'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建目标路径
|
|
|
+ let targetPath = '/';
|
|
|
+ for (let i = 0; i < breadcrumbIndex; i++) {
|
|
|
+ targetPath += pathParts[i] + '/';
|
|
|
+ }
|
|
|
+
|
|
|
+ resolve(targetPath);
|
|
|
+ } catch (error) {
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 返回上级目录
|
|
|
+ public async goBack(): Promise<void> {
|
|
|
+ if (this.pathHistory.length === 0) {
|
|
|
+ Logger.info(TAG, '已经在根目录,无法返回');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从历史记录中取出上一级路径
|
|
|
+ const previousPath = this.pathHistory.pop();
|
|
|
+ if (previousPath !== undefined) {
|
|
|
+ await this.loadFilesInfoFromWebdav(previousPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取面包屑路径数组
|
|
|
+ public getBreadcrumbs(): string[] {
|
|
|
+ if (!this.currentPath || this.currentPath === '/') {
|
|
|
+ return ['根目录'];
|
|
|
+ }
|
|
|
+
|
|
|
+ const parts = this.currentPath.split('/').filter(part => part !== '');
|
|
|
+ const breadcrumbs = ['根目录'];
|
|
|
+
|
|
|
+ for (let i = 0; i < parts.length; i++) {
|
|
|
+ breadcrumbs.push(parts[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return breadcrumbs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 是否可以返回上级
|
|
|
+ public canGoBack(): boolean {
|
|
|
+ return this.pathHistory.length > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== Preferences操作 ====================
|
|
|
+
|
|
|
+ // 加载历史数据(从Preferences迁移)
|
|
|
+ public async loadInfo(): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 这里可以添加从Preferences加载历史配置的逻辑
|
|
|
+ Logger.info(TAG, '从Preferences加载配置');
|
|
|
+ } catch (error) {
|
|
|
+ Logger.error(TAG, '从Preferences加载配置失败:', error.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存配置到Preferences
|
|
|
+ public async saveInfo(): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 这里可以添加保存配置到Preferences的逻辑
|
|
|
+ Logger.info(TAG, '保存配置到Preferences');
|
|
|
+ } catch (error) {
|
|
|
+ Logger.error(TAG, '保存配置到Preferences失败:', error.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 下载队列管理 ====================
|
|
|
+
|
|
|
+ // 添加到下载队列
|
|
|
+ public addToDownloadQueue(song: VideoItem, account: WebDavAccount): void {
|
|
|
+ const task: TransferTask = { song, account };
|
|
|
+ this.downloadQueue.push(task);
|
|
|
+ Logger.info(TAG, '添加到下载队列:', song.name);
|
|
|
+ this.notifyObservers(WebdavManagerStates.DownloadQueueChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从下载队列移除
|
|
|
+ public removeFromDownloadQueue(index: number): void {
|
|
|
+ if (index >= 0 && index < this.downloadQueue.length) {
|
|
|
+ const task = this.downloadQueue[index];
|
|
|
+ this.downloadQueue.splice(index, 1);
|
|
|
+ Logger.info(TAG, '从下载队列移除:', task.song.name);
|
|
|
+ this.notifyObservers(WebdavManagerStates.DownloadQueueChanged);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空下载队列
|
|
|
+ public clearDownloadQueue(): void {
|
|
|
+ this.downloadQueue = [];
|
|
|
+ Logger.info(TAG, '清空下载队列');
|
|
|
+ this.notifyObservers(WebdavManagerStates.DownloadQueueChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 上传队列管理 ====================
|
|
|
+
|
|
|
+ // 添加到上传队列
|
|
|
+ public addToUploadQueue(song: VideoItem, account: WebDavAccount): void {
|
|
|
+ const task: TransferTask = { song, account };
|
|
|
+ this.uploadQueue.push(task);
|
|
|
+ Logger.info(TAG, '添加到上传队列:', song.name);
|
|
|
+ this.notifyObservers(WebdavManagerStates.UploadQueueChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从上传队列移除
|
|
|
+ public removeFromUploadQueue(index: number): void {
|
|
|
+ if (index >= 0 && index < this.uploadQueue.length) {
|
|
|
+ const task = this.uploadQueue[index];
|
|
|
+ this.uploadQueue.splice(index, 1);
|
|
|
+ Logger.info(TAG, '从上传队列移除:', task.song.name);
|
|
|
+ this.notifyObservers(WebdavManagerStates.UploadQueueChanged);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空上传队列
|
|
|
+ public clearUploadQueue(): void {
|
|
|
+ this.uploadQueue = [];
|
|
|
+ Logger.info(TAG, '清空上传队列');
|
|
|
+ this.notifyObservers(WebdavManagerStates.UploadQueueChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 安全认证方法 ====================
|
|
|
+
|
|
|
+ // 获取WebDAV认证信息(用于播放器)
|
|
|
+ public async getWebDavAuthHeaders(accountId: string): Promise<WebDavAuthInfo | null> {
|
|
|
+ const account = await this.getWebDavAccountById(accountId);
|
|
|
+ if (!account || !account.account || !account.password) {
|
|
|
+ Logger.error(TAG, '无效的WebDAV账户或缺少认证信息');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建基础URL
|
|
|
+ const protocol: string = account.enableHttps ? 'https' : 'http';
|
|
|
+ const host: string = account.isUseLocalHost && account.localHost ? account.localHost : account.host;
|
|
|
+ const port: number = account.port;
|
|
|
+
|
|
|
+ // 构建认证头
|
|
|
+ const credentials = buffer
|
|
|
+ .from(`${account.account}:${account.password}`)
|
|
|
+ .toString("base64");
|
|
|
+
|
|
|
+ const authInfo: WebDavAuthInfo = {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Basic ${credentials}`,
|
|
|
+ 'User-Agent': 'TTMusic/1.0'
|
|
|
+ },
|
|
|
+ url: `${protocol}://${host}:${port}`,
|
|
|
+ accountId: accountId
|
|
|
+ };
|
|
|
+ return authInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 构建HTTP请求头,特别处理WebDAV认证
|
|
|
+ * @param currentSong - 当前播放的歌曲信息
|
|
|
+ * @param videoUrl - 当前歌曲的URL
|
|
|
+ * @param webDavAuthItem - 当前WebDAV认证信息(实例变量)
|
|
|
+ * @returns Promise<Map<string, string>> HTTP请求头
|
|
|
+ */
|
|
|
+export async function buildHttpHeadersWithWebDav(
|
|
|
+ currentSong: VideoItem | undefined,
|
|
|
+ videoUrl: string,
|
|
|
+ webDavAuthItem: WebDavAuthItem
|
|
|
+): Promise<Map<string, string>> {
|
|
|
+ const headers = new Map<string, string>();
|
|
|
+
|
|
|
+ let isWebDavSong = false;
|
|
|
+
|
|
|
+ if (currentSong) {
|
|
|
+ Logger.info(`heanup 当前歌曲信息 - name: ${currentSong.name}, type: ${currentSong.type}, filePath: ${currentSong.filePath}`);
|
|
|
+
|
|
|
+ // 检查是否为WebDAV歌曲
|
|
|
+ if (currentSong.type === CommonConstants.TYPE_WEBDAV) {
|
|
|
+ isWebDavSong = true;
|
|
|
+
|
|
|
+ // 优先从实例变量获取认证信息
|
|
|
+ if (webDavAuthItem) {
|
|
|
+ Logger.info(`heanup 从实例变量获取到WebDAV认证信息,账户ID: ${webDavAuthItem.accountId}`);
|
|
|
+ } else {
|
|
|
+ // 回退到全局上下文
|
|
|
+ try {
|
|
|
+ const globalContext = GlobalContext.getContext();
|
|
|
+ webDavAuthItem = globalContext.getObject('webDavAuthInfo') as WebDavAuthItem;
|
|
|
+ if (webDavAuthItem) {
|
|
|
+ Logger.info(`heanup 从全局上下文获取到WebDAV认证信息,账户ID: ${webDavAuthItem.accountId}`);
|
|
|
+ } else {
|
|
|
+ Logger.warn(`heanup 全局上下文中没有WebDAV认证信息`);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ Logger.error(`heanup 获取全局上下文失败:`, error.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果识别为WebDAV但没有认证信息,记录警告
|
|
|
+ if (!webDavAuthItem) {
|
|
|
+ Logger.warn(`heanup 识别为WebDAV播放但缺少认证信息,可能需要手动配置WebDAV账户`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isWebDavSong && webDavAuthItem) {
|
|
|
+ Logger.info(`heanup 检测到WebDAV播放,添加安全认证头`);
|
|
|
+ Logger.info(`heanup 歌曲URL: ${videoUrl}`);
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (webDavAuthItem && webDavAuthItem.accountId) {
|
|
|
+ Logger.info(`heanup 找到WebDAV认证信息,账户ID: ${webDavAuthItem.accountId}`);
|
|
|
+ // 使用WebdavManager获取认证头
|
|
|
+ const webdavManager = WebdavManager.getInstance();
|
|
|
+ const authHeaders = await webdavManager.getWebDavAuthHeaders(webDavAuthItem.accountId.toString());
|
|
|
+
|
|
|
+ if (authHeaders) {
|
|
|
+ // 添加Basic认证头(使用规范字段名)
|
|
|
+ headers.set("Authorization", authHeaders.headers.Authorization);
|
|
|
+ } else {
|
|
|
+ Logger.error(`heanup 无法获取WebDAV认证头`);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Logger.error(`heanup WebDAV认证信息无效或缺少accountId`);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ Logger.error(`heanup 获取WebDAV认证信息失败:`, error.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加标准的WebDAV请求头
|
|
|
+ headers.set("User-Agent", "TTMusic-WebDAV/1.0");
|
|
|
+ headers.set("Accept", "*/*");
|
|
|
+ // 请求首段数据,触发服务器返回 206(部分内容)以适配流式播放
|
|
|
+ headers.set("Range", "bytes=0-");
|
|
|
+
|
|
|
+ Logger.info(`heanup WebDAV安全认证头设置完成`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 输出所有设置的头部信息用于调试
|
|
|
+ console.log(`heanup 设置的HTTP头部信息:`);
|
|
|
+ const headerIterator = headers.entries();
|
|
|
+ let headerEntry = headerIterator.next();
|
|
|
+ while (!headerEntry.done) {
|
|
|
+ const key = headerEntry.value[0];
|
|
|
+ const value = headerEntry.value[1];
|
|
|
+ console.log(`heanup ${key}: ${value}`);
|
|
|
+ headerEntry = headerIterator.next();
|
|
|
+ }
|
|
|
+
|
|
|
+ return headers;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * WebDAV认证信息(LocalMusic专用)
|
|
|
+ */
|
|
|
+export interface WebDavAuthItem {
|
|
|
+ accountId: number;
|
|
|
+ host: string;
|
|
|
+ port: number;
|
|
|
+ account: string;
|
|
|
+ password: string;
|
|
|
+ enableHttps: boolean;
|
|
|
+}
|