| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556 |
- 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
- || this.driveType === RemoteDriveType.AudioStation
- || this.driveType === RemoteDriveType.Plex) {
- 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.driveType === RemoteDriveType.AudioStation
- || this.driveType === RemoteDriveType.Plex)) {
- 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('AudioStation', RemoteDriveType.AudioStation);
- this.buildTypeButton('Plex', RemoteDriveType.Plex);
- 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.AudioStation:
- return '新建AudioStation';
- case RemoteDriveType.Plex:
- return '新建Plex';
- 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.AudioStation:
- return '可粘贴 AudioStation 连接(如 https://user:pass@host:5001),自动填充参数';
- case RemoteDriveType.Plex:
- return '可粘贴 Plex 连接(如 http://user:pass@host:32400),自动填充参数';
- 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.AudioStation) {
- return this.enableHttps ? 5001 : 5000;
- }
- if (this.driveType === RemoteDriveType.Plex) {
- return 32400;
- }
- 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.AudioStation:
- return $r('app.media.cloudDisk');
- case RemoteDriveType.Plex:
- return $r('app.media.cloudDisk');
- 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');
- }
|