| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528 |
- import { ToastUtil } from '@pura/harmony-utils';
- import { CommonConstants } from '../common/constants/CommonConstants';
- import { RemoteDriveType } from '../common/enums/RemoteDriveType';
- import { WebDavAccount } from '../viewmodel/WebDavAccount';
- import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
- import Logger from '../common/util/Logger';
- import { ConfigurationConstant, Context } from '@kit.AbilityKit';
- import { BaiduConstants } from '../common/constants/BaiduConstants';
- import { BaiduDeviceTokenResult, pollDeviceToken as pollBaiduDeviceToken, requestDeviceCode as requestBaiduDeviceCode } from '../common/network/BaiduPanClient';
- import { webview } from '@kit.ArkWeb';
- interface ParsedConnectionParts {
- protocol: string;
- username?: string;
- password?: string;
- host: string;
- port?: number;
- path?: string;
- }
- interface BaiduTokenResultShape {
- access_token?: string;
- refresh_token?: string;
- expires_in?: number;
- error?: string;
- error_description?: string;
- }
- // 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
- function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
- if (isDarkMode) {
- // 深色模式下返回更深的灰色或半透明黑色
- return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`;
- }
- const color = themeColor.replace('#', '');
- const r = parseInt(color.substring(0, 2), 16);
- const g = parseInt(color.substring(2, 4), 16);
- const b = parseInt(color.substring(4, 6), 16);
- return `rgba(${r},${g},${b},${alpha})`;
- }
- // 网盘账户对话框
- @Component
- export struct RemoteDriveAccountDialog {
- @Prop isEditMode: boolean = false;
- @Prop account: WebDavAccount;
- onConfirm?: (account: WebDavAccount) => void;
- onCancel?: () => void;
- @State accountName: string = '我的网盘';
- @State host: string = '';
- @State port: number = -1;
- @State filepath: string = '/';
- @State username: string = '';
- @State password: string = '';
- @State enableHttps: boolean = false;
- @Prop initialDriveType: RemoteDriveType = RemoteDriveType.WebDav;
- @State driveType: RemoteDriveType = RemoteDriveType.WebDav;
- @State shareName: string = '';
- @State domain: string = '';
- @State navidromeBasePath: string = '/rest';
- @State jellyfinBasePath: string = '';
- @State embyBasePath: string = '';
- @State coverPath: string = '';
- @State isDarkMode: boolean = false;
- @State ftpEncoding: string = 'utf-8';
- @State baiduAuthStatus: string = '';
- @State baiduAccessToken: string = '';
- @State baiduRefreshToken: string = '';
- @State baiduTokenExpiresAt: number = 0;
- @State showBaiduAuthDialog: boolean = false;
- @State baiduAuthorizeUrl: string = '';
- @State baiduAuthProgress: number = 0;
- @State baiduAuthMode: 'web' | 'device' = 'web';
- @State baiduDeviceCode: string = '';
- @State baiduDeviceUserCode: string = '';
- @State baiduDeviceVerifyUrl: string = '';
- @State baiduDeviceQrUrl: string = '';
- @State baiduDeviceExpireAt: number = 0;
- @State baiduDeviceInterval: number = 5;
- @State baiduDevicePolling: boolean = false;
- @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
- @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
- ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
- @State portInput: string = '';
- private nameCustomized: boolean = false;
- private portCustomized: boolean = false;
- private navBasePathCustomized: boolean = false;
- private jellyfinBasePathCustomized: boolean = false;
- private embyBasePathCustomized: boolean = false;
- private baiduAuthStateToken: string = '';
- private baiduWebController: webview.WebviewController = new webview.WebviewController();
- private baiduDevicePollTimer: number = 0;
- onColorModeChange() {
- this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
- }
- aboutToAppear(): void {
- console.info('heanup driveType = ' + this.driveType);
- this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
- if (this.isEditMode) {
- this.accountName = this.account.name;
- this.host = this.account.host;
- this.updatePortState(this.account.port, true);
- this.filepath = this.account.filepath;
- this.username = this.account.account;
- this.password = this.account.password;
- this.enableHttps = this.account.enableHttps;
- this.driveType = this.account.webType ?? RemoteDriveType.WebDav;
- this.shareName = this.account.smbShare ?? '';
- this.domain = this.account.smbDomain ?? '';
- this.coverPath = this.account.coverPath || '';
- this.ftpEncoding = this.account.ftpEncoding ?? 'utf-8';
- this.baiduAccessToken = this.account.baiduAccessToken ?? '';
- this.baiduRefreshToken = this.account.baiduRefreshToken ?? '';
- this.baiduTokenExpiresAt = this.account.baiduTokenExpiresAt ?? 0;
- this.nameCustomized = true;
- if (this.driveType === RemoteDriveType.Navidrome) {
- this.navidromeBasePath = this.normalizeNavidromeBasePath(this.account.navidromeBasePath ?? '/rest');
- this.navBasePathCustomized = true;
- }
- if (this.driveType === RemoteDriveType.Jellyfin) {
- this.jellyfinBasePath = this.normalizeJellyfinBasePath(this.account.jellyfinBasePath ?? '');
- this.jellyfinBasePathCustomized = true;
- }
- if (this.driveType === RemoteDriveType.Emby) {
- this.embyBasePath = this.normalizeEmbyBasePath(this.account.embyBasePath ?? '');
- this.embyBasePathCustomized = true;
- }
- Logger.info('heanup RemoteDriveAccountDialog', `编辑模式加载账户: ${this.accountName}, 封面路径: ${this.coverPath}`);
- if (this.coverPath) {
- Logger.info('heanup RemoteDriveAccountDialog', `封面文件是否存在: ${ImagePickerUtil.imageExists(this.coverPath)}`);
- }
- } else {
- this.driveType = this.initialDriveType ?? RemoteDriveType.WebDav;
- this.baiduAccessToken = '';
- this.baiduRefreshToken = '';
- this.baiduTokenExpiresAt = 0;
- this.jellyfinBasePathCustomized = false;
- this.navBasePathCustomized = false;
- this.handleDriveTypeChange(this.driveType, true);
- }
- }
- aboutToDisappear(): void {
- this.closeBaiduAuthDialog();
- }
- build() {
- Stack() {
- Scroll() {
- this.contentBuilder();
- }
- .height('100%')
- .width('100%')
- .scrollBar(BarState.Off);
- if (this.showBaiduAuthDialog) {
- this.buildBaiduAuthDialogOverlay();
- }
- }
- .height('100%')
- .width('100%');
- }
- @Builder
- contentBuilder() {
- Column({ space: 16 }) {
- // 标题
- Text(this.isEditMode ? '编辑账户' : '添加账户')
- .fontSize(18)
- .fontWeight(FontWeight.Bold)
- .fontColor($r('app.color.index_tab_font_color'));
- // 类型选择 + 封面
- // this.buildTypeSelector();
- // 账户封面选择
- Row({ space: 8 }) {
- Text('账户封面')
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666')
- .fontWeight(FontWeight.Medium)
- .alignSelf(ItemAlign.Center);
- // 封面选择区域
- Row({ space: 12 }) {
- // 封面预览
- Stack() {
- if (this.coverPath) {
- Image(this.coverPath)
- .width(50)
- .height(50)
- .borderRadius(8)
- .objectFit(ImageFit.Cover);
- } else {
- // 默认封面图标
- Column() {
- Image(getCloudDiskIcon(this.driveType))
- .width(30)
- .height(30)
- }
- .width(50)
- .height(50)
- .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
- .borderRadius(8)
- .justifyContent(FlexAlign.Center);
- }
- }
- .onClick(() => {
- this.handleSelectCover();
- });
- // 操作按钮
- Column({ space: 8 }) {
- Button('选择封面(可选)')
- .width(120)
- .height(36)
- .fontSize(11)
- .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
- .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
- .onClick(() => {
- this.handleSelectCover();
- });
- if (this.coverPath) {
- Button('移除封面')
- .width(120)
- .height(36)
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#FF453A' : Color.Red)
- .backgroundColor(this.isDarkMode ? 'rgba(255,69,58,0.2)' : '#FFE5E5')
- .onClick(() => {
- this.handleRemoveCover();
- });
- }
- }
- .alignItems(HorizontalAlign.Start);
- }
- .width('100%')
- .justifyContent(FlexAlign.Start);
- }
- .width('100%')
- .alignItems(VerticalAlign.Center);
- // 账户名称
- Row({ space: 8 }) {
- Text('名称')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextArea({ placeholder: '请输入账户名称', text: this.accountName })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.accountName = value;
- this.nameCustomized = true;
- });
- }
- .width('100%')
- .alignItems(VerticalAlign.Center);
- if (this.driveType === RemoteDriveType.Baidu) {
- this.buildBaiduAuthSection();
- } else {
- // 服务器地址
- Row({ space: 8 }) {
- Text(this.getServerLabel())
- .fontSize(14)
- .maxLines(2)
- .fontColor($r('app.color.index_tab_font_color'));
- TextArea({ placeholder: '服务器地址或连接串', text: this.host })
- .layoutWeight(1)
- .onChange((value: string) => {
- const trimmed = value.trim();
- if (trimmed.includes('://') && this.tryParseConnectionString(trimmed)) {
- return;
- }
- this.host = trimmed;
- });
- }
- .alignItems(VerticalAlign.Center);
- this.buildHelperText(this.getServerHint())
- // 端口
- Row({ space: 8 }) {
- Text('端口')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: this.getPortPlaceholder(), text: this.portInput })
- .layoutWeight(1)
- .maxLines(1)
- .type(InputType.Number)
- .onChange((value: string) => {
- const trimmed = value.trim();
- this.portInput = trimmed;
- const defaultPort = this.getDefaultPort();
- if (trimmed.length === 0) {
- this.portCustomized = false;
- this.port = defaultPort;
- return;
- }
- const parsed = parseInt(trimmed);
- if (Number.isNaN(parsed) || parsed === -1) {
- return;
- }
- this.updatePortState(parsed, true, false);
- });
- }
- .alignItems(VerticalAlign.Center);
- // this.buildHelperText('若留空将自动使用协议默认端口')
- if (this.driveType === RemoteDriveType.Smb) {
- // SMB 共享名称
- Row({ space: 8 }) {
- Text('共享名称')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '共享名称', text: this.shareName })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.shareName = value;
- });
- }
- .alignItems(VerticalAlign.Center)
- .visibility(Visibility.Visible)
- // this.buildHelperText('NAS 上共享根目录的名称,例如 music、share')
- Row({ space: 8 }) {
- Text('域/工作组')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '可选项', text: this.domain })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.domain = value;
- });
- }
- .alignItems(VerticalAlign.Center)
- .visibility(Visibility.Visible)
- // this.buildHelperText('可选,用于需要域/工作组认证的 SMB 服务器')
- }
- if (this.driveType === RemoteDriveType.Ftp) {
- Row({ space: 8 }) {
- Text('编码')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '默认 utf-8,可填 gbk 等', text: this.ftpEncoding })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.ftpEncoding = value.trim();
- });
- }
- .alignItems(VerticalAlign.Center);
- this.buildHelperText('若目录或文件名为中文,可根据服务器设置调整编码');
- }
- if (this.driveType === RemoteDriveType.Jellyfin) {
- Row({ space: 8 }) {
- Text('API路径')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '可选,如 /jellyfin', text: this.jellyfinBasePath })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.jellyfinBasePath = this.normalizeJellyfinBasePath(value);
- this.jellyfinBasePathCustomized = true;
- });
- }
- .alignItems(VerticalAlign.Center);
- this.buildHelperText('Jellyfin 部署在子路径时填写,默认留空表示根路径');
- }
- if (this.driveType === RemoteDriveType.Emby) {
- Row({ space: 8 }) {
- Text('API路径')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '可选,如 /emby', text: this.embyBasePath })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.embyBasePath = this.normalizeEmbyBasePath(value);
- this.embyBasePathCustomized = true;
- });
- }
- .alignItems(VerticalAlign.Center);
- this.buildHelperText('Emby 部署在子路径时填写,默认留空表示根路径');
- }
- // if (this.driveType === RemoteDriveType.Navidrome) {
- // Row({ space: 8 }) {
- // Text('API路径')
- // .fontSize(14)
- // .fontColor($r('app.color.index_tab_font_color'));
- // TextInput({ placeholder: '/rest', text: this.navidromeBasePath })
- // .layoutWeight(1)
- // .maxLines(1)
- // .onChange((value: string) => {
- // this.navidromeBasePath = this.normalizeNavidromeBasePath(value);
- // this.navBasePathCustomized = true;
- // });
- // }
- // .alignItems(VerticalAlign.Center);
- // this.buildHelperText('Navidrome/Subsonic REST 前缀,默认 /rest,可填 /navidrome/rest 等路径')
- // }
- // 文件目录
- Row({ space: 8 }) {
- Text('文件目录')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '远程目录', text: this.filepath })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.filepath = value;
- });
- }
- .alignItems(VerticalAlign.Center)
- .visibility(this.driveType === RemoteDriveType.Navidrome || this.driveType === RemoteDriveType.Jellyfin || this.driveType === RemoteDriveType.Emby
- ? Visibility.None
- : Visibility.Visible)
- // this.buildHelperText('从共享根开始的路径,例如 /music 或 /音乐/歌单1')
- Row({ space: 8 }) {
- Text('用户名')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '请输入用户名', text: this.username })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.username = value;
- });
- }
- .alignItems(VerticalAlign.Center);
- Row({ space: 8 }) {
- Text('密码')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '请输入密码', text: this.password })
- .type(InputType.Password)
- .layoutWeight(1)
- .maxLines(2)
- .onChange((value: string) => {
- this.password = value;
- });
- }
- .alignItems(VerticalAlign.Center);
- if (this.driveType === RemoteDriveType.WebDav || this.driveType === RemoteDriveType.Jellyfin || this.driveType === RemoteDriveType.Emby) {
- Row({ space: 12 }) {
- Text('启用HTTPS')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- Toggle({ type: ToggleType.Switch, isOn: this.enableHttps })
- .selectedColor(this.themeColor)
- .onChange((isOn: boolean) => {
- this.enableHttps = isOn;
- if (!this.portCustomized &&
- (this.driveType === RemoteDriveType.WebDav || this.driveType === RemoteDriveType.Jellyfin || this.driveType === RemoteDriveType.Emby)) {
- this.updatePortState(this.getDefaultPort(), false);
- }
- });
- }
- .width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- .alignItems(VerticalAlign.Center);
- }
- } // end non-baidu section
- // 按钮
- Row({ space: 12 }) {
- Button('取消', { type: ButtonType.Capsule })
- .backgroundColor($r('app.color.cancel_button_background'))
- .fontColor($r('app.color.cancel_button_text'))
- .layoutWeight(1)
- .onClick(() => {
- this.onCancel?.();
- });
- Button(this.isEditMode ? '保存' : '添加', { type: ButtonType.Capsule })
- .backgroundColor(this.themeColor)
- .layoutWeight(1)
- .onClick(() => {
- const needHost = this.driveType !== RemoteDriveType.Baidu;
- if (!this.accountName || (needHost && !this.host)) {
- ToastUtil.showToast('请填写账户名称和服务器地址');
- return;
- }
- if (this.driveType === RemoteDriveType.Baidu && !this.baiduAccessToken) {
- ToastUtil.showToast('请先完成百度网盘授权');
- return;
- }
- const updatedAccount = new WebDavAccount();
- if (this.isEditMode) {
- updatedAccount.id = this.account.id;
- }
- updatedAccount.name = this.accountName;
- updatedAccount.host = this.driveType === RemoteDriveType.Baidu ? 'pan.baidu.com' : this.host;
- updatedAccount.port = this.driveType === RemoteDriveType.Baidu ? 443 : this.port;
- updatedAccount.filepath = this.filepath;
- updatedAccount.account = this.driveType === RemoteDriveType.Baidu ? '' : this.username;
- updatedAccount.password = this.driveType === RemoteDriveType.Baidu ? '' : this.password;
- updatedAccount.enableHttps = this.enableHttps;
- updatedAccount.coverPath = this.coverPath;
- updatedAccount.isActivate = true;
- updatedAccount.localHost = '';
- updatedAccount.isUseLocalHost = false;
- updatedAccount.lyricFilePath = '';
- updatedAccount.uploadFilePath = '';
- updatedAccount.imageFilePath = '';
- updatedAccount.webType = this.driveType;
- updatedAccount.smbShare = this.shareName;
- updatedAccount.smbDomain = this.domain;
- updatedAccount.navidromeBasePath = this.normalizeNavidromeBasePath(this.navidromeBasePath);
- updatedAccount.jellyfinBasePath = this.normalizeJellyfinBasePath(this.jellyfinBasePath);
- updatedAccount.embyBasePath = this.normalizeEmbyBasePath(this.embyBasePath);
- updatedAccount.ftpEncoding = this.ftpEncoding && this.ftpEncoding.length > 0 ? this.ftpEncoding : 'utf-8';
- updatedAccount.baiduAccessToken = this.baiduAccessToken;
- updatedAccount.baiduRefreshToken = this.baiduRefreshToken;
- updatedAccount.baiduTokenExpiresAt = this.baiduTokenExpiresAt;
- this.onConfirm?.(updatedAccount);
- });
- }
- .width('100%')
- .margin({ top: 8 });
- }
- .padding(24)
- .width('90%')
- .borderRadius(16);
- }
- @Builder
- private buildTypeSelector() {
- Column({ space: 8 }) {
- Text('协议类型')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'))
- .fontWeight(FontWeight.Medium)
- .alignSelf(ItemAlign.Start);
- Row({ space: 12 }) {
- this.buildTypeButton('WebDAV', RemoteDriveType.WebDav);
- this.buildTypeButton('SMB', RemoteDriveType.Smb);
- this.buildTypeButton('Navidrome', RemoteDriveType.Navidrome);
- this.buildTypeButton('Jellyfin', RemoteDriveType.Jellyfin);
- this.buildTypeButton('Emby', RemoteDriveType.Emby);
- this.buildTypeButton('FTP', RemoteDriveType.Ftp);
- this.buildTypeButton('百度网盘', RemoteDriveType.Baidu);
- }
- .width('100%');
- }
- .width('100%');
- }
- @Builder
- private buildTypeButton(label: string, type: RemoteDriveType) {
- Button(label)
- .type(ButtonType.Capsule)
- .backgroundColor(this.driveType === type ? this.themeColor : (this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background')))
- .fontColor(this.driveType === type ? Color.White : $r('app.color.index_tab_font_color'))
- .onClick(() => {
- this.handleDriveTypeChange(type);
- });
- }
- private handleDriveTypeChange(type: RemoteDriveType, forceApply: boolean = false) {
- const changed = this.driveType !== type;
- if (!changed && !forceApply) {
- return;
- }
- if (this.driveType === RemoteDriveType.Baidu && type !== RemoteDriveType.Baidu) {
- this.closeBaiduAuthDialog();
- }
- this.driveType = type;
- if (type === RemoteDriveType.Smb || type === RemoteDriveType.Ftp) {
- this.enableHttps = false;
- }
- this.applyTypeDefaults(type, true);
- }
- private applyTypeDefaults(type: RemoteDriveType, forcePort: boolean = false) {
- if (!this.nameCustomized) {
- this.accountName = this.getDefaultAccountName(type);
- }
- if (type === RemoteDriveType.Navidrome && !this.navBasePathCustomized) {
- this.navidromeBasePath = '/rest';
- }
- if (type === RemoteDriveType.Jellyfin && !this.jellyfinBasePathCustomized) {
- this.jellyfinBasePath = '';
- }
- if (type === RemoteDriveType.Emby && !this.embyBasePathCustomized) {
- this.embyBasePath = '';
- }
- if ((type === RemoteDriveType.Jellyfin || type === RemoteDriveType.Emby) && (!this.filepath || this.filepath.length === 0)) {
- this.filepath = '/';
- }
- if (type === RemoteDriveType.Ftp && (!this.ftpEncoding || this.ftpEncoding.length === 0)) {
- this.ftpEncoding = 'utf-8';
- }
- if (type === RemoteDriveType.Baidu) {
- this.enableHttps = true;
- this.host = 'pan.baidu.com';
- this.username = '';
- this.password = '';
- this.shareName = '';
- this.domain = '';
- if (!this.filepath || this.filepath.length === 0) {
- this.filepath = '/';
- }
- }
- this.updatePortForType(type, forcePort);
- }
- private updatePortForType(type: RemoteDriveType, force: boolean = false) {
- if (!force && this.portCustomized) {
- return;
- }
- this.updatePortState(this.getDefaultPort(), false);
- }
- private getDefaultAccountName(type: RemoteDriveType): string {
- switch (type) {
- case RemoteDriveType.Smb:
- return '新建SMB';
- case RemoteDriveType.Navidrome:
- return '新建Navidrome';
- case RemoteDriveType.Jellyfin:
- return '新建Jellyfin';
- case RemoteDriveType.Emby:
- return '新建Emby';
- case RemoteDriveType.Ftp:
- return '新建FTP';
- case RemoteDriveType.Baidu:
- return '百度网盘';
- default:
- return '新建WebDAV';
- }
- }
- private getServerLabel(): string {
- return '服务器';
- }
- private getServerHint(): string {
- switch (this.driveType) {
- case RemoteDriveType.Smb:
- return '支持 smb://user:pass@host/share 输入,自动拆分账户、共享、路径';
- case RemoteDriveType.Navidrome:
- return '可粘贴 Navidrome/Subsonic 连接(如 https://user:pass@host:4533/rest),自动填充参数';
- case RemoteDriveType.Jellyfin:
- return '可粘贴 Jellyfin 连接(如 https://user:pass@host:8096/jellyfin),自动填充参数';
- case RemoteDriveType.Emby:
- return '可粘贴 Emby 连接(如 https://user:pass@host:8096/emby),自动填充参数';
- case RemoteDriveType.Ftp:
- return '支持 ftp://user:pass@host:21/path 输入,自动填充账户和目录';
- case RemoteDriveType.Baidu:
- return '百度网盘无需服务器地址,请使用下方按钮完成授权';
- default:
- return '支持 https://user:pass@host:port/path WebDAV 连接串,自动填充账户、端口和目录';
- }
- }
- private getPortPlaceholder(): string {
- return `默认: ${this.getDefaultPort()}`;
- }
- private getDefaultPort(): number {
- if (this.driveType === RemoteDriveType.Smb) {
- return 445;
- }
- if (this.driveType === RemoteDriveType.Navidrome) {
- return this.enableHttps ? 443 : 4533;
- }
- if (this.driveType === RemoteDriveType.Jellyfin) {
- return this.enableHttps ? 8920 : 8096;
- }
- if (this.driveType === RemoteDriveType.Emby) {
- return this.enableHttps ? 8920 : 8096;
- }
- if (this.driveType === RemoteDriveType.Ftp) {
- return 21;
- }
- if (this.driveType === RemoteDriveType.Baidu) {
- return 443;
- }
- return this.enableHttps ? 443 : 5005;
- }
- private updatePortState(value: number, customized: boolean, syncInput: boolean = true) {
- this.port = value;
- this.portCustomized = customized;
- if (syncInput) {
- this.portInput = value > 0 ? value.toString() : '';
- }
- }
- @Builder
- private buildHelperText(text: string) {
- if (text && text.trim().length > 0) {
- Text(text)
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
- .opacity(0.9)
- .alignSelf(ItemAlign.Start)
- }
- }
- @Builder
- private buildFileDirectoryRow() {
- Row({ space: 8 }) {
- Text('文件目录')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'));
- TextInput({ placeholder: '远程目录', text: this.filepath })
- .layoutWeight(1)
- .maxLines(1)
- .onChange((value: string) => {
- this.filepath = value;
- });
- }
- .alignItems(VerticalAlign.Center);
- }
- private tryParseConnectionString(input: string): boolean {
- const parsed = this.parseConnectionString(input);
- if (!parsed) {
- return false;
- }
- const protocol = parsed.protocol.toLowerCase();
- if (protocol === 'smb' || protocol === 'cifs') {
- if (this.driveType !== RemoteDriveType.Smb) {
- this.handleDriveTypeChange(RemoteDriveType.Smb);
- }
- this.applyParsedSmb(parsed);
- ToastUtil.showToast('已解析 SMB 连接');
- return true;
- }
- if (protocol === 'http' || protocol === 'https') {
- if (Number(this.driveType) === RemoteDriveType.Jellyfin) {
- this.applyParsedJellyfin(parsed);
- ToastUtil.showToast('已解析 Jellyfin 连接');
- return true;
- }
- if (Number(this.driveType) === RemoteDriveType.Emby) {
- this.applyParsedEmby(parsed);
- ToastUtil.showToast('已解析 Emby 连接');
- return true;
- }
- if (Number(this.driveType) === RemoteDriveType.Navidrome) {
- this.applyParsedNavidrome(parsed);
- ToastUtil.showToast('已解析 Navidrome 连接');
- return true;
- }
- const looksNav = this.guessNavidromePath(parsed.path);
- if (looksNav) {
- if (Number(this.driveType) !== RemoteDriveType.Navidrome) {
- this.handleDriveTypeChange(RemoteDriveType.Navidrome);
- }
- this.applyParsedNavidrome(parsed);
- ToastUtil.showToast('已解析 Navidrome 连接');
- return true;
- }
- const looksJellyfin = this.guessJellyfinPath(parsed.path);
- if (looksJellyfin) {
- if (Number(this.driveType) !== RemoteDriveType.Jellyfin) {
- this.handleDriveTypeChange(RemoteDriveType.Jellyfin);
- }
- this.applyParsedJellyfin(parsed);
- ToastUtil.showToast('已解析 Jellyfin 连接');
- return true;
- }
- const looksEmby = this.guessEmbyPath(parsed.path);
- if (looksEmby) {
- if (Number(this.driveType) !== RemoteDriveType.Emby) {
- this.handleDriveTypeChange(RemoteDriveType.Emby);
- }
- this.applyParsedEmby(parsed);
- ToastUtil.showToast('已解析 Emby 连接');
- return true;
- }
- if (this.driveType !== RemoteDriveType.WebDav) {
- this.handleDriveTypeChange(RemoteDriveType.WebDav);
- }
- this.applyParsedWebDav(parsed);
- ToastUtil.showToast('已解析 WebDAV 连接');
- return true;
- }
- if (protocol === 'ftp' || protocol === 'ftps') {
- if (this.driveType !== RemoteDriveType.Ftp) {
- this.handleDriveTypeChange(RemoteDriveType.Ftp);
- }
- this.applyParsedFtp(parsed);
- ToastUtil.showToast('已解析 FTP 连接');
- return true;
- }
- return false;
- }
- private applyParsedWebDav(parsed: ParsedConnectionParts): void {
- this.enableHttps = parsed.protocol === 'https';
- this.host = parsed.host;
- if (parsed.port) {
- this.updatePortState(parsed.port, true);
- } else {
- this.updatePortForType(RemoteDriveType.WebDav, true);
- }
- if (parsed.username) {
- this.username = parsed.username;
- }
- if (parsed.password) {
- this.password = parsed.password;
- }
- this.filepath = parsed.path?.length ? parsed.path : '/';
- }
- private applyParsedSmb(parsed: ParsedConnectionParts): void {
- this.host = parsed.host;
- if (parsed.port) {
- this.updatePortState(parsed.port, true);
- } else {
- this.updatePortForType(RemoteDriveType.Smb, true);
- }
- if (parsed.username) {
- this.username = parsed.username;
- }
- if (parsed.password) {
- this.password = parsed.password;
- }
- const segments = parsed.path?.replace(/^\/+/, '').split('/') ?? [];
- this.shareName = segments.length > 0 && segments[0].length > 0 ? segments[0] : this.shareName;
- const relative = segments.length > 1 ? segments.slice(1).join('/') : '';
- this.filepath = relative ? `/${relative}` : '/';
- }
- private applyParsedNavidrome(parsed: ParsedConnectionParts): void {
- this.enableHttps = parsed.protocol === 'https';
- this.host = parsed.host;
- if (parsed.port) {
- this.updatePortState(parsed.port, true);
- } else {
- this.updatePortForType(RemoteDriveType.Navidrome, true);
- }
- if (parsed.username) {
- this.username = parsed.username;
- }
- if (parsed.password) {
- this.password = parsed.password;
- }
- this.navidromeBasePath = this.normalizeNavidromeBasePath(parsed.path ?? this.navidromeBasePath);
- this.navBasePathCustomized = true;
- this.filepath = '/';
- }
- private applyParsedJellyfin(parsed: ParsedConnectionParts): void {
- this.enableHttps = parsed.protocol === 'https';
- this.host = parsed.host;
- if (parsed.port) {
- this.updatePortState(parsed.port, true);
- } else {
- this.updatePortForType(RemoteDriveType.Jellyfin, true);
- }
- if (parsed.username) {
- this.username = parsed.username;
- }
- if (parsed.password) {
- this.password = parsed.password;
- }
- this.jellyfinBasePath = this.normalizeJellyfinBasePath(parsed.path ?? this.jellyfinBasePath);
- this.jellyfinBasePathCustomized = true;
- this.filepath = '/';
- }
- private applyParsedEmby(parsed: ParsedConnectionParts): void {
- this.enableHttps = parsed.protocol === 'https';
- this.host = parsed.host;
- if (parsed.port) {
- this.updatePortState(parsed.port, true);
- } else {
- this.updatePortForType(RemoteDriveType.Emby, true);
- }
- if (parsed.username) {
- this.username = parsed.username;
- }
- if (parsed.password) {
- this.password = parsed.password;
- }
- this.embyBasePath = this.normalizeEmbyBasePath(parsed.path ?? this.embyBasePath);
- this.embyBasePathCustomized = true;
- this.filepath = '/';
- }
- private applyParsedFtp(parsed: ParsedConnectionParts): void {
- this.enableHttps = false;
- this.host = parsed.host;
- if (parsed.port) {
- this.updatePortState(parsed.port, true);
- } else {
- this.updatePortForType(RemoteDriveType.Ftp, true);
- }
- if (parsed.username) {
- this.username = parsed.username;
- }
- if (parsed.password) {
- this.password = parsed.password;
- }
- this.filepath = parsed.path && parsed.path.length > 0 ? parsed.path : '/';
- }
- private guessNavidromePath(path?: string): boolean {
- if (!path) {
- return false;
- }
- const lower = path.toLowerCase();
- return lower.includes('/rest') || lower.includes('navidrome');
- }
- private guessJellyfinPath(path?: string): boolean {
- if (!path) {
- return false;
- }
- return path.toLowerCase().includes('jellyfin');
- }
- private guessEmbyPath(path?: string): boolean {
- if (!path) {
- return false;
- }
- return path.toLowerCase().includes('emby');
- }
- private normalizeNavidromeBasePath(value: string): string {
- if (!value || value.trim().length === 0) {
- return '/rest';
- }
- let normalized = value.trim();
- if (!normalized.startsWith('/')) {
- normalized = `/${normalized}`;
- }
- if (normalized.length > 1 && normalized.endsWith('/')) {
- normalized = normalized.slice(0, -1);
- }
- return normalized.length === 0 ? '/rest' : normalized;
- }
- private normalizeJellyfinBasePath(value: string): string {
- if (!value || value.trim().length === 0) {
- return '';
- }
- let normalized = value.trim();
- if (!normalized.startsWith('/')) {
- normalized = `/${normalized}`;
- }
- if (normalized.length > 1 && normalized.endsWith('/')) {
- normalized = normalized.slice(0, -1);
- }
- return normalized === '/' ? '' : normalized;
- }
- private normalizeEmbyBasePath(value: string): string {
- if (!value || value.trim().length === 0) {
- return '';
- }
- let normalized = value.trim();
- if (!normalized.startsWith('/')) {
- normalized = `/${normalized}`;
- }
- if (normalized.length > 1 && normalized.endsWith('/')) {
- normalized = normalized.slice(0, -1);
- }
- return normalized === '/' ? '' : normalized;
- }
- private parseConnectionString(input: string): ParsedConnectionParts | null {
- const pattern = /^([a-z][a-z0-9+\-.]*):\/\/(?:([^:@\/]+)(?::([^@\/]*))?@)?([^\/:]+)(?::(\d+))?(\/.*)?$/i;
- const match = input.match(pattern);
- if (!match) {
- Logger.warn('heanup RemoteDriveAccountDialog', `连接字符串解析失败: ${input}`);
- return null;
- }
- const protocol = match[1].toLowerCase();
- const username = match[2] ? this.safeDecode(match[2]) : undefined;
- const password = match[3] ? this.safeDecode(match[3]) : undefined;
- const host = match[4];
- const port = match[5] ? Number(match[5]) : undefined;
- const path = match[6] && match[6].length > 0 ? match[6] : undefined;
- return { protocol, username, password, host, port, path };
- }
- private safeDecode(value: string): string {
- try {
- return decodeURIComponent(value);
- } catch (_error) {
- return value;
- }
- }
- @Builder
- private buildBaiduAuthSection() {
- Column({ space: 12 }) {
- Text('百度账号授权')
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- .fontColor($r('app.color.index_tab_font_color'))
- .alignSelf(ItemAlign.Start);
- Row({ space: 8 }) {
- Button('扫码授权')
- .type(ButtonType.Capsule)
- .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
- .fontColor($r('app.color.index_tab_font_color'))
- .onClick(() => {
- this.handleBaiduDeviceAuthorize();
- });
- Button('打开百度授权页')
- .type(ButtonType.Capsule)
- .backgroundColor(this.themeColor)
- .fontColor(Color.White)
- .onClick(() => {
- this.handleBaiduImplicitAuthorize();
- });
- if (this.baiduAccessToken) {
- Button('清除授权')
- .type(ButtonType.Capsule)
- .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
- .fontColor($r('app.color.index_tab_font_color'))
- .onClick(() => {
- this.baiduAccessToken = '';
- this.baiduRefreshToken = '';
- this.baiduTokenExpiresAt = 0;
- this.baiduAuthStatus = '已清除授权信息';
- this.closeBaiduAuthDialog();
- });
- }
- }
- Text('可扫码授权或打开百度网盘授权网页,请登录并允许访问网盘文件。')
- .fontSize(13)
- .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666')
- .maxLines(2);
- if (this.baiduAuthStatus) {
- Text(this.baiduAuthStatus)
- .fontSize(13)
- .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666')
- .maxLines(2);
- }
- if (this.baiduAccessToken) {
- Text(`已获取 Access Token,${this.baiduTokenExpiresAt > 0 ? `将在 ${this.getBaiduExpireText()} 过期` : '有效期未知'}`)
- .fontSize(13)
- .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666');
- }
- }
- .width('100%');
- }
- @Builder
- private buildBaiduAuthDialogOverlay() {
- Stack() {
- Column()
- .width('100%')
- .height('100%')
- .backgroundColor('rgba(0,0,0,0.5)')
- .onClick(() => {
- this.closeBaiduAuthDialog();
- });
- Column({ space: 8 }) {
- Row() {
- Text('百度网盘授权')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? Color.White : Color.Black);
- Text('关闭')
- .fontSize(14)
- .fontColor(this.themeColor)
- .onClick(() => {
- this.closeBaiduAuthDialog();
- });
- }
- .width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- .alignItems(VerticalAlign.Center)
- .padding({ bottom: 4 });
- this.buildBaiduAuthModeSwitch();
- Divider()
- .color(this.isDarkMode ? '#3A3A3C' : '#E5E5EA');
- if (this.baiduAuthMode === 'device') {
- this.buildBaiduDeviceAuthContent();
- } else {
- if (this.baiduAuthProgress > 0 && this.baiduAuthProgress < 100) {
- Text(`页面加载中 ${this.baiduAuthProgress}%`)
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666');
- }
- Web({
- src: this.baiduAuthorizeUrl,
- controller: this.baiduWebController
- })
- .layoutWeight(1)
- .width('100%')
- .javaScriptAccess(true)
- .domStorageAccess(true)
- .onPageEnd((event) => {
- if (event && event.url) {
- this.handleBaiduAuthNavigation(event.url);
- }
- })
- .onProgressChange((event) => {
- if (event) {
- this.baiduAuthProgress = event.newProgress;
- }
- });
- }
- }
- .width('92%')
- .height('85%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#1C1C1E' : Color.White)
- .borderRadius(16);
- }
- .width('100%')
- .height('100%');
- }
- @Builder
- private buildBaiduAuthModeSwitch() {
- Row({ space: 8 }) {
- this.buildBaiduAuthModeChip('网页登录', 'web');
- this.buildBaiduAuthModeChip('扫码登录', 'device');
- }
- .width('100%')
- .padding({ top: 4, bottom: 4 });
- }
- @Builder
- private buildBaiduAuthModeChip(label: string, mode: 'web' | 'device') {
- Button(label)
- .type(ButtonType.Capsule)
- .backgroundColor(this.baiduAuthMode === mode ? this.themeColor : (this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background')))
- .fontColor(this.baiduAuthMode === mode ? Color.White : $r('app.color.index_tab_font_color'))
- .onClick(() => {
- this.switchBaiduAuthMode(mode);
- });
- }
- @Builder
- private buildBaiduDeviceAuthContent() {
- Column({ space: 12 }) {
- if (this.baiduDeviceQrUrl) {
- Image(this.baiduDeviceQrUrl)
- .width(220)
- .height(220)
- .borderRadius(12)
- .objectFit(ImageFit.Contain)
- .alignSelf(ItemAlign.Center);
- } else {
- Column() {
- Text('正在获取二维码...')
- .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666');
- }
- .width(220)
- .height(220)
- .alignSelf(ItemAlign.Center)
- .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
- .borderRadius(12)
- .justifyContent(FlexAlign.Center)
- .alignItems(HorizontalAlign.Center);
- }
- if (this.baiduDeviceUserCode) {
- Text(`设备码: ${this.baiduDeviceUserCode}`)
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- .fontColor($r('app.color.index_tab_font_color'));
- }
- if (this.baiduDeviceVerifyUrl) {
- Text(`或在浏览器访问 ${this.baiduDeviceVerifyUrl} 输入设备码完成授权`)
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666')
- .maxLines(2);
- }
- if (this.baiduDeviceExpireAt) {
- Text(`二维码将于 ${this.getBaiduDeviceRemainText()} 过期`)
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666');
- }
- if (this.baiduAuthStatus) {
- Text(this.baiduAuthStatus)
- .fontSize(13)
- .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666')
- .maxLines(2);
- }
- Row({ space: 12 }) {
- Button('刷新二维码')
- .type(ButtonType.Capsule)
- .backgroundColor(this.themeColor)
- .fontColor(Color.White)
- .onClick(() => {
- this.handleBaiduDeviceAuthorize();
- });
- Button('改用网页登录')
- .type(ButtonType.Capsule)
- .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
- .fontColor($r('app.color.index_tab_font_color'))
- .onClick(() => {
- this.handleBaiduImplicitAuthorize();
- });
- }
- .width('100%')
- .justifyContent(FlexAlign.Start);
- }
- .layoutWeight(1)
- .width('100%');
- }
- private getBaiduExpireText(): string {
- if (!this.baiduTokenExpiresAt) {
- return '';
- }
- const remain = Math.max(0, this.baiduTokenExpiresAt - Date.now());
- const hours = Math.floor(remain / 3600000);
- const minutes = Math.floor((remain % 3600000) / 60000);
- return hours > 0 ? `${hours}小时${minutes}分后` : `${minutes}分钟后`;
- }
- private switchBaiduAuthMode(mode: 'web' | 'device'): void {
- if (mode === this.baiduAuthMode && this.showBaiduAuthDialog) {
- return;
- }
- if (mode === 'device') {
- this.handleBaiduDeviceAuthorize();
- } else {
- this.handleBaiduImplicitAuthorize();
- }
- }
- private async handleBaiduDeviceAuthorize(): Promise<void> {
- this.stopBaiduDevicePolling();
- this.baiduAuthMode = 'device';
- this.resetBaiduDeviceState(false);
- this.baiduAuthorizeUrl = '';
- this.baiduAuthProgress = 0;
- this.baiduAuthStateToken = '';
- this.baiduAuthStatus = '正在获取扫码授权信息...';
- try {
- const codeInfo = await requestBaiduDeviceCode();
- this.baiduDeviceCode = codeInfo.device_code;
- this.baiduDeviceUserCode = codeInfo.user_code;
- this.baiduDeviceVerifyUrl = codeInfo.verification_url;
- this.baiduDeviceQrUrl = codeInfo.qrcode_url ?? '';
- this.baiduDeviceExpireAt = Date.now() + (codeInfo.expires_in ?? 0) * 1000;
- this.baiduDeviceInterval = Math.max(3, codeInfo.interval ?? 5);
- this.baiduAuthStatus = '请在百度网盘App中扫码确认或输入设备码授权';
- this.showBaiduAuthDialog = true;
- this.startBaiduDevicePolling();
- } catch (error) {
- const message = (error as Error).message ?? '请求失败';
- this.baiduAuthStatus = `获取设备码失败: ${message}`;
- ToastUtil.showToast('获取百度扫码授权失败');
- }
- }
- private startBaiduDevicePolling(): void {
- this.stopBaiduDevicePolling();
- if (!this.baiduDeviceCode) {
- return;
- }
- const intervalMs = Math.max(3000, this.baiduDeviceInterval * 1000);
- this.baiduDevicePollTimer = setInterval(() => {
- this.queryBaiduDeviceToken();
- }, intervalMs) as number;
- }
- private stopBaiduDevicePolling(): void {
- if (this.baiduDevicePollTimer) {
- clearInterval(this.baiduDevicePollTimer);
- this.baiduDevicePollTimer = 0;
- }
- this.baiduDevicePolling = false;
- }
- private async queryBaiduDeviceToken(): Promise<void> {
- if (!this.baiduDeviceCode || this.baiduDevicePolling) {
- return;
- }
- this.baiduDevicePolling = true;
- try {
- const result: BaiduDeviceTokenResult = await pollBaiduDeviceToken(this.baiduDeviceCode);
- const tokenResult = result as BaiduTokenResultShape;
- if (tokenResult.access_token !== undefined && tokenResult.access_token.length > 0) {
- this.baiduAccessToken = tokenResult.access_token;
- this.baiduRefreshToken = tokenResult.refresh_token ?? '';
- this.baiduTokenExpiresAt = tokenResult.expires_in ? Date.now() + tokenResult.expires_in * 1000 : 0;
- this.baiduAuthStatus = '授权成功,可保存账户';
- ToastUtil.showToast('百度授权成功');
- this.closeBaiduAuthDialog();
- return;
- }
- const errorResult = result as BaiduTokenResultShape;
- if (errorResult.error === undefined) {
- return;
- }
- switch (errorResult.error) {
- case 'authorization_pending':
- this.baiduAuthStatus = '请在百度网盘App扫码并确认';
- break;
- case 'slow_down':
- this.baiduAuthStatus = '请求过于频繁,稍后自动重试';
- this.baiduDeviceInterval += 2;
- this.startBaiduDevicePolling();
- break;
- case 'expired_token':
- case 'invalid_grant':
- this.baiduAuthStatus = '二维码已过期,请刷新重新扫码';
- this.stopBaiduDevicePolling();
- break;
- default:
- this.baiduAuthStatus = `授权失败: ${errorResult.error_description ?? errorResult.error}`;
- this.stopBaiduDevicePolling();
- break;
- }
- } catch (error) {
- const message = (error as Error).message ?? '未知错误';
- this.baiduAuthStatus = `授权轮询失败: ${message}`;
- } finally {
- this.baiduDevicePolling = false;
- }
- }
- private getBaiduDeviceRemainText(): string {
- if (!this.baiduDeviceExpireAt) {
- return '稍后';
- }
- const remain = Math.max(0, this.baiduDeviceExpireAt - Date.now());
- const minutes = Math.floor(remain / 60000);
- const seconds = Math.floor((remain % 60000) / 1000);
- return minutes > 0 ? `${minutes}分${seconds}秒后` : `${seconds}秒后`;
- }
- private resetBaiduDeviceState(resetMode: boolean = true): void {
- if (resetMode) {
- this.baiduAuthMode = 'web';
- }
- this.baiduDeviceCode = '';
- this.baiduDeviceUserCode = '';
- this.baiduDeviceVerifyUrl = '';
- this.baiduDeviceQrUrl = '';
- this.baiduDeviceExpireAt = 0;
- this.baiduDeviceInterval = 5;
- this.baiduDevicePolling = false;
- }
- private handleBaiduImplicitAuthorize(): void {
- this.stopBaiduDevicePolling();
- this.resetBaiduDeviceState(false);
- this.baiduAuthMode = 'web';
- this.baiduAuthStatus = '正在打开百度授权页面...';
- this.baiduAuthStateToken = `${Date.now()}`;
- this.baiduAuthProgress = 0;
- this.baiduAuthorizeUrl = this.buildBaiduAuthorizeUrl(this.baiduAuthStateToken);
- this.showBaiduAuthDialog = true;
- }
- private closeBaiduAuthDialog(): void {
- this.showBaiduAuthDialog = false;
- this.baiduAuthorizeUrl = '';
- this.baiduAuthProgress = 0;
- this.baiduAuthStateToken = '';
- this.stopBaiduDevicePolling();
- this.resetBaiduDeviceState();
- }
- private buildBaiduAuthorizeUrl(state: string): string {
- const params = [
- `response_type=token`,
- `client_id=${encodeURIComponent(BaiduConstants.APP_KEY)}`,
- `redirect_uri=${encodeURIComponent(BaiduConstants.OOB_REDIRECT_PARAM)}`,
- `scope=${encodeURIComponent(BaiduConstants.AUTH_SCOPE)}`,
- `display=mobile`,
- `state=${encodeURIComponent(state)}`
- ];
- return `${BaiduConstants.AUTHORIZE_URL}?${params.join('&')}`;
- }
- private handleBaiduAuthNavigation(url: string): void {
- if (!url) {
- return;
- }
- const loginSuccessHttps = BaiduConstants.LOGIN_SUCCESS_URL.replace('http://', 'https://');
- if (!url.startsWith(BaiduConstants.LOGIN_SUCCESS_URL) && !url.startsWith(loginSuccessHttps)) {
- return;
- }
- const fragmentIndex = url.indexOf('#');
- const fragment = fragmentIndex >= 0 ? url.substring(fragmentIndex + 1) : '';
- if (!fragment) {
- return;
- }
- const params = this.parseAuthFragment(fragment);
- const responseState = params.get('state') ?? '';
- if (this.baiduAuthStateToken && responseState && responseState !== this.baiduAuthStateToken) {
- this.baiduAuthStatus = '授权状态校验失败,请重试';
- this.closeBaiduAuthDialog();
- return;
- }
- const error = params.get('error');
- if (error) {
- const description = params.get('error_description') ?? error;
- this.baiduAuthStatus = `授权失败: ${description}`;
- this.closeBaiduAuthDialog();
- return;
- }
- const accessToken = params.get('access_token');
- if (!accessToken) {
- return;
- }
- const expiresIn = Number(params.get('expires_in') ?? '0');
- this.baiduAccessToken = accessToken;
- this.baiduRefreshToken = '';
- this.baiduTokenExpiresAt = expiresIn > 0 ? Date.now() + expiresIn * 1000 : 0;
- this.baiduAuthStatus = '授权成功,可保存账户';
- ToastUtil.showToast('百度授权成功');
- this.closeBaiduAuthDialog();
- }
- private parseAuthFragment(fragment: string): Map<string, string> {
- const params = new Map<string, string>();
- const pairs = fragment.split('&');
- for (let i = 0; i < pairs.length; i++) {
- const pair = pairs[i];
- if (!pair || pair.length === 0) {
- continue;
- }
- const separator = pair.indexOf('=');
- const key = separator >= 0 ? pair.substring(0, separator) : pair;
- const value = separator >= 0 ? pair.substring(separator + 1) : '';
- params.set(decodeURIComponent(key), decodeURIComponent(value));
- }
- return params;
- }
- /**
- * 处理选择封面
- */
- private async handleSelectCover() {
- try {
- // 需要获取上下文,这里通过全局上下文获取
- const context = getContext(this) as Context
- if (!context) {
- ToastUtil.showToast('获取应用上下文失败')
- return
- }
- const selectedPath = await ImagePickerUtil.selectSingleWebDavCover(context)
- if (selectedPath) {
- // 如果之前有封面,删除旧封面
- if (this.coverPath && this.coverPath !== this.account.coverPath) {
- ImagePickerUtil.deleteImage(this.coverPath)
- }
- this.coverPath = selectedPath
- Logger.info('heanup RemoteDriveAccountDialog', `选择封面成功: ${selectedPath}`)
- }
- } catch (error) {
- Logger.error('heanup RemoteDriveAccountDialog', `选择封面失败: ${(error as Error).message}`)
- ToastUtil.showToast('选择封面失败')
- }
- }
- /**
- * 处理移除封面
- */
- private handleRemoveCover() {
- if (this.coverPath) {
- if(!this.account){
- this.coverPath = ''
- return
- }
- // 只有当封面不是原来的封面时才删除文件
- if (this.coverPath !== this.account.coverPath) {
- ImagePickerUtil.deleteImage(this.coverPath)
- }
- this.coverPath = ''
- Logger.info('heanup RemoteDriveAccountDialog', '移除封面成功')
- }
- }
- }
- export function getCloudDiskIcon(type: number): ResourceStr {
- switch ( type)
- {
- case RemoteDriveType.WebDav:
- return $r('app.media.webdav');
- case RemoteDriveType.Smb:
- return $r('app.media.smb');
- case RemoteDriveType.Ftp:
- return $r('app.media.ftp');
- case RemoteDriveType.Navidrome:
- return $r('app.media.navidrome');
- case RemoteDriveType.Jellyfin:
- return $r('app.media.jellyfin');
- case RemoteDriveType.Emby:
- return $r('app.media.emby');
- case RemoteDriveType.Baidu:
- return $r('app.media.baiduwp');
- case RemoteDriveType.ALi:
- return $r('app.media.ali_disk');
- case RemoteDriveType.DISK_123:
- return $r('app.media.ali_disk');
- }
- return $r('app.media.cloudDisk');
- }
|