ScanFilePage.ets 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. import TitleBar from '../view/TitleBar'
  2. import { router, window } from '@kit.ArkUI'
  3. import { AppUtil, DeviceUtil, DisplayUtil, FileUtil, LogUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils'
  4. import lottie from '@ohos/lottie'
  5. import { AnimationItem } from '@ohos/lottie'
  6. import { JSON, MessageEvents, taskpool, worker } from '@kit.ArkTS'
  7. import { fileUri, picker } from '@kit.CoreFileKit'
  8. import { Utility } from '../common/util/Utility'
  9. import MediaTable from '../common/util/MediaTable'
  10. import { VideoItem } from '../viewmodel/VideoItem'
  11. import Logger from '../common/util/Logger'
  12. import { CommonConstants, STR_LOCK_VIDEO } from '../common/constants/CommonConstants'
  13. import { EventConstants } from '../common/constants/EventConstants'
  14. import { BusinessError, emitter } from '@kit.BasicServicesKit'
  15. import { BreakpointTypeEnum } from '../common/util/BreakpointSystem'
  16. import { resourceManager } from '@kit.LocalizationKit'
  17. import { common, ConfigurationConstant } from '@kit.AbilityKit'
  18. import { DialogHelper } from '@pura/harmony-dialog'
  19. import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
  20. @Preview
  21. // @Entry
  22. @Component
  23. export struct ScanFilePage{
  24. @StorageProp('topSafeHeight') topSafeHeight: number = 0;
  25. // 新增状态变量
  26. @State currentInsertCount: number = 0 // 当前已扫描并插入的歌曲数量
  27. @State currentFilePath: string = '' // 当前正在处理的文件路径
  28. @State invalidMusicCount: number = 0 // 无效音乐文件数量
  29. @State isCorrecting: boolean = false // 是否正在校正数据
  30. context = this.getUIContext().getHostContext() as common.UIAbilityContext
  31. // @StorageProp('mediaKuList') mediaKuList: Array<VideoItem> = []; //媒体库文件
  32. @State rootPath:string = '' //音频根目录
  33. @State lockPath:string = ''
  34. @Consume isShowDrawer: boolean;
  35. @Consume offsetX: number;
  36. @State textVisi:Visibility=Visibility.Hidden
  37. @State isStart:boolean = false
  38. @State isStartCover:boolean = false
  39. @State isStartSync:boolean = false
  40. @State appName:string = ''
  41. @State packName: string = ''
  42. @Consume mType: number;
  43. //lottie动画构建渲染上下文
  44. private mainRenderingSettings: RenderingContextSettings = new RenderingContextSettings(true)
  45. private path:string = "common/lottie/leida.json"
  46. private successpath:string = "common/lottie/success2.json"
  47. private mainCanvasRenderingContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.mainRenderingSettings)
  48. private animateItem: AnimationItem | null = null;
  49. /** 当前断点类型(如大屏/小屏) */
  50. @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
  51. @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  52. @State isDarkMode: boolean = false
  53. @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
  54. ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
  55. private windowClass: window.Window = globalThis.windowClass
  56. @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight());
  57. initLottie(lpath:string,isLoop:boolean){
  58. lottie.destroy(); //加载动画前先销毁之前加载的动画
  59. this.animateItem = lottie.loadAnimation({
  60. container: this.mainCanvasRenderingContext, // 渲染上下文
  61. renderer: 'canvas', // 渲染方式
  62. loop: isLoop, // 是否循环播放,默认true
  63. autoplay: true, // 是否自动播放,默认true
  64. name: '2024', // 动画名称
  65. contentMode: 'Contain', // 填充的模式
  66. frameRate: 30, //设置animator的刷帧率为30
  67. imagePath: 'lottie/images/', // 加载读取指定路径下的图片资源
  68. path: lpath, // json路径
  69. // initialSegment: [0,70]
  70. })
  71. lottie.setSpeed(0.7)
  72. }
  73. onPageShow() {
  74. }
  75. // 组件生命周期
  76. async aboutToAppear() {
  77. this.packName = AppUtil.getBundleName()
  78. this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
  79. Utility.getAppName(getContext(this)).then((appName:string)=>{
  80. this.appName = appName
  81. })
  82. let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
  83. AppStorage.setOrCreate('themeColor', themeColor);
  84. this.themeColor = themeColor;
  85. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  86. const documentViewPicker = new picker.DocumentViewPicker()
  87. let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD })
  88. this.rootPath = new fileUri.FileUri(documentSaveResult[0]).path
  89. this.lockPath = this.rootPath +'/'+ STR_LOCK_VIDEO
  90. LogUtil.info('onecold 文件扫描 aboutToAppear')
  91. this.initLottie(this.path,true)
  92. setTimeout(()=>{
  93. lottie.pause()
  94. },88)
  95. setTimeout(()=>{
  96. // 显示校正数据对话框
  97. this.showCorrectDataDialog(false)
  98. },30000)
  99. }
  100. onColorModeChange() {
  101. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  102. }
  103. endScan(isOnekey:boolean){
  104. const eventData: emitter.EventData = {};
  105. emitter.emit({ eventId: EventConstants.EVENT_SCAN_UPDATE }, eventData); // 发送视频打开广播事件
  106. this.watchStatus(false)
  107. this.currentFilePath = '扫描文件入库成功!'
  108. if(isOnekey){
  109. this.isStartCover = false
  110. }else{
  111. if(this.isStartSync){
  112. this.isStartSync = false
  113. }else{
  114. this.isStart = false
  115. }
  116. }
  117. //结束动画
  118. lottie.pause()
  119. lottie.stop()
  120. this.initLottie(this.successpath,false)
  121. lottie.play()
  122. }
  123. //扫描过程中屏幕要保存常亮
  124. watchStatus(isKeep:boolean) {
  125. this.windowClass.setWindowKeepScreenOn(isKeep);
  126. }
  127. // 组件生命周期
  128. aboutToDisappear() {
  129. lottie.destroy();
  130. }
  131. initState(isOnekey:boolean){
  132. this.textVisi = Visibility.Visible
  133. this.initLottie(this.path,true)
  134. if(isOnekey){
  135. this.isStartCover = true
  136. }else{
  137. this.isStart = true
  138. }
  139. lottie.play()
  140. }
  141. doOptimize(isOnekey:boolean){
  142. this.watchStatus(true)
  143. this.initState(isOnekey)
  144. const task = new taskpool.Task(scanDirectoryTask, this.context, this.rootPath,
  145. this.lockPath,PreferencesUtil.getStringSync('COVER_API',''),
  146. PreferencesUtil.getBooleanSync('autoParseMusicName', true));
  147. task.onReceiveData((path: string, count: number) => {
  148. this.currentFilePath = path
  149. this.currentInsertCount = count
  150. })
  151. taskpool.execute(task, taskpool.Priority.HIGH).then(()=>{
  152. this.endScan(isOnekey)
  153. taskpool.terminateTask(task);
  154. }).catch((e:object)=>{
  155. console.info("onecold task1 catch e: " + JSON.stringify(e));
  156. })
  157. }
  158. // 更新进度回调函数
  159. private updateProgress = (path: string, count: number) => {
  160. this.currentFilePath = path
  161. this.currentInsertCount = count
  162. }
  163. @Builder
  164. topTitleBar(){
  165. Column() {
  166. Row({ space: 15 }) {
  167. //左侧滑动按钮
  168. Button({ type: ButtonType.Circle, stateEffect: true }) {
  169. SymbolGlyph($r('sys.symbol.sort'))
  170. .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
  171. }
  172. .attributeModifier(new ButtonFancyModifier(40, 40))
  173. .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
  174. .animation({ duration: 300, curve: Curve.Ease })
  175. .onClick(() => {
  176. this.getUIContext().animateTo({ duration: 555 }, () => {
  177. // 动画闭包内控制Image组件的出现和消失
  178. this.isShowDrawer = !this.isShowDrawer
  179. this.offsetX = 0
  180. })
  181. })
  182. .attributeModifier(new ShadowModifier())
  183. .zIndex(0)
  184. Text($r('app.string.file_scan'))
  185. .margin({left:3,right:10})
  186. .fontColor($r('app.color.text_color'))
  187. .fontSize(19)
  188. .maxLines(1)
  189. .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动
  190. .layoutWeight(1)
  191. .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
  192. Button({ type: ButtonType.Circle, stateEffect: true }) {
  193. SymbolGlyph($r('sys.symbol.exclamationmark_circle'))
  194. .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
  195. }
  196. .attributeModifier(new ButtonFancyModifier(40, 40))
  197. .animation({ duration: 300, curve: Curve.Ease })
  198. .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
  199. .attributeModifier(new ShadowModifier())
  200. .zIndex(0)
  201. .onClick(() => {
  202. // 显示校正数据对话框
  203. this.showCorrectDataDialog(true)
  204. })
  205. }
  206. }
  207. .padding({ top: this.topSafeHeight+10, left: 10, right: 10,bottom:12 })
  208. .width('100%')
  209. }
  210. build() {
  211. Column() {
  212. // 顶部安全区和自定义标题栏
  213. // 顶部安全区和自定义标题栏
  214. Column() {
  215. this.topTitleBar()
  216. }
  217. Scroll(){
  218. Column() {
  219. Stack(){
  220. Image($r('app.media.file_search'))
  221. .width(48)
  222. .height(48)
  223. .fillColor(this.themeColor)
  224. .alignSelf(ItemAlign.Center)
  225. //lottie动画
  226. Canvas(this.mainCanvasRenderingContext)
  227. .width(160)
  228. .height(160)
  229. .onReady(()=>{
  230. //抗锯齿的设置
  231. this.mainCanvasRenderingContext.imageSmoothingEnabled = true;
  232. this.mainCanvasRenderingContext.imageSmoothingQuality = 'medium'
  233. })
  234. }
  235. .margin({top:10})
  236. .transition(TransitionEffect.move(TransitionEdge.END)
  237. .animation({ duration: 380, curve: Curve.Ease,delay:300 }))
  238. Text(`${this.currentInsertCount}首歌`)
  239. .height(38)
  240. .fontSize(16)
  241. .fontColor($r('app.color.text_color'))
  242. .fontWeight(480)
  243. .visibility(this.textVisi)
  244. .animation({
  245. duration: 666,
  246. curve: 'ease-in-out' // 可选动画曲线
  247. })
  248. Text(this.currentFilePath)
  249. .height(38)
  250. .fontSize(!this.isStart?15:12)
  251. .maxLines(3)
  252. .margin({bottom:6})
  253. .width('86%')
  254. .textAlign(this.isStart?TextAlign.Start:TextAlign.Center)
  255. .fontColor($r('app.color.text_color'))
  256. .fontWeight(480)
  257. .visibility(this.textVisi)
  258. .animation({
  259. duration: 666,
  260. curve: 'ease-in-out' // 可选动画曲线
  261. })
  262. Button({ type: ButtonType.Capsule, stateEffect: true }) {
  263. Row(){
  264. SymbolGlyph($r('sys.symbol.magnifyingglass'))
  265. .fontSize(22)
  266. .fontColor([Color.White])
  267. Text($r('app.string.start_scan'))
  268. .margin({ left: 8 })
  269. .fontSize(18)
  270. .fontColor(Color.White)
  271. .fontWeight(480)
  272. .textAlign(TextAlign.Center)
  273. }
  274. .justifyContent(FlexAlign.Center)
  275. }
  276. .width(180)
  277. .height(55)
  278. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
  279. .margin({ top: 10, bottom: 10 })
  280. .backgroundColor(this.themeColor)
  281. .enabled(this.isStart ?false:true)
  282. .onClick(() => {
  283. this.doOptimize(false)
  284. })
  285. .alignSelf(ItemAlign.Center)
  286. .transition(TransitionEffect.move(TransitionEdge.END)
  287. .animation({ duration: 380, curve: Curve.Ease,delay:300 }))
  288. Column() {
  289. Row() {
  290. Image($r('app.media.selected'))
  291. .width(16)
  292. .height(16)
  293. .fillColor(this.themeColor)
  294. .margin({ left:10})
  295. .alignSelf(ItemAlign.Center)
  296. Text(Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one'))
  297. +`${this.appName} `+
  298. Utility.resourceToString(getContext(this),$r('app.string.file_scan_tip_one_1')))
  299. .margin({ left: 10, right: 20 })
  300. .fontSize(15)
  301. .fontColor(Color.Gray)
  302. .fontWeight(480)
  303. .layoutWeight(1)
  304. }
  305. // .width('100%')
  306. .margin({ left:25,right: 25 ,top:20 })
  307. Row() {
  308. Image($r('app.media.selected'))
  309. .width(16)
  310. .height(16)
  311. .fillColor(this.themeColor)
  312. .margin({ left:10})
  313. .alignSelf(ItemAlign.Center)
  314. Text($r('app.string.file_scan_tip_two'))
  315. .margin({ left: 10, right: 20 })
  316. .fontSize(15)
  317. .fontColor(Color.Gray)
  318. .fontWeight(480)
  319. .layoutWeight(1)
  320. }
  321. // .width('100%')
  322. .margin({ left:25,right: 25 ,top:20,bottom:20 })
  323. Row() {
  324. Image($r('app.media.selected'))
  325. .width(16)
  326. .height(16)
  327. .fillColor(this.themeColor)
  328. .margin({ left:10})
  329. .alignSelf(ItemAlign.Center)
  330. Text(Utility.resourceToString(getContext(this),$r('app.string.pcfile_scan_tip_one'))
  331. +`${this.packName} `+
  332. Utility.resourceToString(getContext(this),$r('app.string.pcfile_scan_tip_one_1')))
  333. .margin({ left: 10, right: 20 })
  334. .fontSize(15)
  335. .fontColor(Color.Gray)
  336. .fontWeight(480)
  337. .layoutWeight(1)
  338. }
  339. .margin({ left:25,right: 25 ,bottom:20 })
  340. }
  341. .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?450:350)
  342. .margin({ left:25,right: 25,top:20,bottom:20 })
  343. .borderRadius(20)
  344. .border({
  345. color: Color.Gray,
  346. width: 1.8
  347. })
  348. .justifyContent(FlexAlign.Center)
  349. .transition(TransitionEffect.move(TransitionEdge.END)
  350. .animation({ duration: 380, curve: Curve.Ease,delay:300 }))
  351. Row() {
  352. Button({ type: ButtonType.Capsule, stateEffect: true }) {
  353. Row(){
  354. SymbolGlyph($r('sys.symbol.picture'))
  355. .fontSize(19)
  356. .fontColor([Color.White])
  357. Text($r('app.string.onekey_cover'))
  358. .margin({ left: 4 })
  359. .fontSize(15)
  360. .fontColor(Color.White)
  361. .fontWeight(480)
  362. .textAlign(TextAlign.Center)
  363. }
  364. .justifyContent(FlexAlign.Center)
  365. }
  366. .width(150)
  367. .height(55)
  368. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
  369. .margin({ top: 10, bottom: 10,right:10 })
  370. .backgroundColor(this.themeColor)
  371. .enabled(this.isStartCover ?false:true)
  372. .onClick(() => {
  373. if(PreferencesUtil.getStringSync('COVER_API','')===''){
  374. this.showTipsDialog()
  375. return
  376. }
  377. this.doOptimize(true)
  378. })
  379. .alignSelf(ItemAlign.Center)
  380. }
  381. .transition(TransitionEffect.move(TransitionEdge.END)
  382. .animation({ duration: 380, curve: Curve.Ease,delay:300 }))
  383. .visibility(Visibility.None)
  384. }
  385. .height('100%')
  386. .width('100%')
  387. .align(Alignment.Top)
  388. }
  389. .height(1000)
  390. }
  391. }
  392. showTipsDialog() {
  393. DialogHelper.showCustomContentDialog({
  394. dialogId: 'tips',
  395. title: "友情提示",
  396. autoCancel: false, //点击遮障层时,不关闭弹窗
  397. backCancel: true, //点击返回键,不关闭弹窗
  398. contentBuilder: () => {
  399. this.customTipsBuilder("请到设置界面配置封面服务器地址!")
  400. },
  401. buttons: [],
  402. })
  403. }
  404. /**
  405. * 显示校正数据对话框
  406. */
  407. showCorrectDataDialog(isEmptyShowDialog:boolean) {
  408. // 先查询无效音乐数量
  409. this.checkInvalidMusicCount(isEmptyShowDialog)
  410. }
  411. /**
  412. * 检查无效音乐数量并显示对话框
  413. */
  414. async checkInvalidMusicCount(isEmptyShowDialog:boolean) {
  415. try {
  416. const table: MediaTable = new MediaTable(this.context);
  417. console.info('heanup ScanFilePage', `checkInvalidMusicCount`)
  418. await new Promise<void>((resolve, reject) => {
  419. table.getRdbStore(this.context, (err:Error) => {
  420. err ? reject(err) : resolve();
  421. });
  422. });
  423. const allMusic = await this.queryAllLocalMusic(table);
  424. console.info('heanup ScanFilePage', `查询到的所有音乐数量: ${allMusic.length}`)
  425. let invalidCount = 0;
  426. const invalidFilePaths: string[] = [];
  427. for (const item of allMusic) {
  428. // 检查是否是本地音乐(filePath包含包名)
  429. if (item.filePath&&item.type===CommonConstants.TYPE_LOCAL && item.filePath.includes(this.packName)) {
  430. const exists = await FileUtil.accessSync(item.filePath);
  431. if (!exists) {
  432. invalidCount++;
  433. invalidFilePaths.push(item.filePath);
  434. }
  435. }
  436. }
  437. this.invalidMusicCount = invalidCount;
  438. console.info('heanup ScanFilePage', `showCustomContentDialog`)
  439. // 显示校正对话框
  440. if(this.invalidMusicCount>0||isEmptyShowDialog){
  441. DialogHelper.showCustomContentDialog({
  442. dialogId: 'correctData',
  443. title: "校正数据",
  444. autoCancel: true,
  445. backCancel: true,
  446. contentBuilder: () => {
  447. this.correctDataContentBuilder(invalidCount);
  448. },
  449. buttons: [],
  450. });
  451. }
  452. } catch (error) {
  453. Logger.error('heanup ScanFilePage', `检查无效音乐失败: ${JSON.stringify(error)}`);
  454. ToastUtil.showToast('检查失败,请稍后重试');
  455. }
  456. }
  457. /**
  458. * 查询所有本地音乐
  459. */
  460. async queryAllLocalMusic(table: MediaTable): Promise<VideoItem[]> {
  461. return new Promise((resolve, reject) => {
  462. table.query(0, (result: VideoItem[]) => {
  463. resolve(result);
  464. }, true);
  465. });
  466. }
  467. /**
  468. * 执行校正数据
  469. */
  470. async doCorrectData() {
  471. if (this.isCorrecting) {
  472. return;
  473. }
  474. this.isCorrecting = true;
  475. DialogHelper.closeDialog('correctData');
  476. try {
  477. const table: MediaTable = new MediaTable(this.context);
  478. await new Promise<void>((resolve, reject) => {
  479. table.getRdbStore(this.context, (err:Error) => {
  480. err ? reject(err) : resolve();
  481. });
  482. });
  483. const allMusic = await this.queryAllLocalMusic(table);
  484. let deletedCount = 0;
  485. this.currentFilePath = '正在校正数据...';
  486. this.textVisi = Visibility.Visible;
  487. for (const item of allMusic) {
  488. // 检查是否是本地音乐(filePath包含包名)
  489. if (item.filePath && item.filePath.includes(this.packName)) {
  490. const exists = await FileUtil.accessSync(item.filePath);
  491. if (!exists) {
  492. // 文件不存在,删除数据库记录
  493. await new Promise<void>((resolve) => {
  494. table.deleteDataFilePath(item.filePath, (success: boolean) => {
  495. if (success) {
  496. deletedCount++;
  497. Logger.info('heanup ScanFilePage', `删除无效记录: ${item.filePath}`);
  498. }
  499. resolve();
  500. });
  501. });
  502. }
  503. }
  504. }
  505. this.currentFilePath = `校正完成!删除了 ${deletedCount} 条无效记录`;
  506. this.currentInsertCount = deletedCount;
  507. // 显示成功提示
  508. setTimeout(() => {
  509. ToastUtil.showToast(`校正完成,删除了 ${deletedCount} 条无效记录`);
  510. }, 500);
  511. } catch (error) {
  512. Logger.error('heanup ScanFilePage', `校正数据失败: ${JSON.stringify(error)}`);
  513. ToastUtil.showToast('校正失败,请稍后重试');
  514. this.currentFilePath = '校正失败';
  515. } finally {
  516. this.isCorrecting = false;
  517. setTimeout(() => {
  518. this.currentFilePath = '';
  519. this.currentInsertCount = 0;
  520. this.textVisi = Visibility.Hidden;
  521. }, 3000);
  522. }
  523. }
  524. @Builder
  525. customTipsBuilder(content: string) {
  526. Column() {
  527. Text(content)
  528. .fontColor(Color.Gray)
  529. .fontSize(16)
  530. .alignSelf(ItemAlign.Start)
  531. .margin({ bottom: 15 })
  532. .fontSize(16)
  533. Row() {
  534. Button('取消')
  535. .fontColor(Color.White)
  536. .backgroundColor($r('app.color.title_bar_bg'))//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]})
  537. .height(50)
  538. .layoutWeight(1)
  539. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7})
  540. .margin({ right: 6 })
  541. .onClick(() => {
  542. DialogHelper.closeDialog('tips'); //关闭弹框
  543. })
  544. Button('跳转')
  545. .fontColor(Color.White)//.linearGradient( { direction: GradientDirection.Right, colors:[['#ff37a0fc',0.0],['#67e667',0.5],['#f5856e',1.0]]})
  546. .layoutWeight(1)
  547. .height(50)
  548. .backgroundColor($r('app.color.title_bar_bg'))
  549. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7})
  550. .margin({ left: 6 })
  551. .onClick(() => {
  552. DialogHelper.closeDialog('tips'); //关闭弹框
  553. this.mType = 3
  554. // router.pushUrl({
  555. // url: 'pages/SettingPage'
  556. // }, router.RouterMode.Single);
  557. })
  558. }
  559. }
  560. .width("100%")
  561. .padding(10)
  562. }
  563. @Builder
  564. correctDataContentBuilder(invalidCount: number) {
  565. Column() {
  566. if (invalidCount > 0) {
  567. Text(`发现 ${invalidCount} 条无效的音乐记录`)
  568. .fontColor($r('app.color.text_color'))
  569. .fontSize(16)
  570. .margin({ bottom: 10 })
  571. Text('这些音乐文件已被删除或移动,是否要清理这些无效记录?')
  572. .fontColor(Color.Gray)
  573. .fontSize(14)
  574. .margin({ bottom: 20 })
  575. Row() {
  576. Button('取消')
  577. .fontColor(Color.White)
  578. .backgroundColor(Color.Gray)
  579. .height(50)
  580. .layoutWeight(1)
  581. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7})
  582. .margin({ right: 10 })
  583. .onClick(() => {
  584. DialogHelper.closeDialog('correctData');
  585. })
  586. Button('确定')
  587. .fontColor(Color.White)
  588. .backgroundColor(this.themeColor)
  589. .height(50)
  590. .layoutWeight(1)
  591. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7})
  592. .onClick(() => {
  593. this.doCorrectData();
  594. })
  595. }
  596. .width('100%')
  597. } else {
  598. Text('未发现无效的音乐记录')
  599. .fontColor($r('app.color.text_color'))
  600. .fontSize(16)
  601. .margin({ bottom: 20 })
  602. Button('确定')
  603. .fontColor(Color.White)
  604. .backgroundColor(this.themeColor)
  605. .height(50)
  606. .width('100%')
  607. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7})
  608. .onClick(() => {
  609. DialogHelper.closeDialog('correctData');
  610. })
  611. }
  612. }
  613. .width("100%")
  614. .padding(15)
  615. }
  616. }
  617. //扫描数据库校正对比数据是否真实存在,不存在在删除数据
  618. @Concurrent
  619. async function syncDataTask(context: Context, mediaKuList: Array<string>) {
  620. const table: MediaTable = new MediaTable(context);
  621. for (const item of mediaKuList) {
  622. const filePath = item;
  623. // 检查 filePath 是否存在
  624. const exists:boolean = await FileUtil.accessSync(filePath);
  625. // 如果 filePath 不存在,则删除数据库中的数据
  626. if (!exists) {
  627. table.getRdbStore(context, async (err:Error) => {
  628. if (err) {
  629. return;
  630. }
  631. Logger.info('xiaozheng 校正数据开始 this.exists = ' + exists+' filepath = ' +filePath)
  632. await table.deleteDataFilePath(filePath, (result:boolean) => {
  633. if(result){
  634. Logger.info(`xiaozheng Deleted item success with filePath: ${filePath}`);
  635. }
  636. });
  637. })
  638. }
  639. }
  640. }
  641. //扫描文件入库
  642. @Concurrent
  643. async function scanDirectoryTask(context: Context, dirPath: string,
  644. lockPath: string,cover_api:string,autoParseMusicName:boolean) {
  645. const stack: string[] = [dirPath];
  646. const table: MediaTable = new MediaTable(context);
  647. let insertCount = 0
  648. while (stack.length > 0) {
  649. const currentPath = stack.pop()!;
  650. const files = FileUtil.listFileSync(currentPath);
  651. await Promise.all(files.map(async (file) => {
  652. const fPath = `${currentPath}/${file}`;
  653. // console.info('onecold scanDirectoryTask fPath = ' + fPath)
  654. if (fPath === lockPath) return;
  655. if (FileUtil.isDirectory(fPath)) {
  656. stack.push(fPath); // 使用栈结构代替递归
  657. } else {
  658. if (fPath.endsWith('.lrc') || fPath.endsWith('.srt')) return;
  659. // console.info('onecold scanDirectoryTask fPath2 = ' + fPath)
  660. if (Utility.isMeidaByExtension(fPath)) {
  661. const mediaItem = await Utility.uriGetMusicAssetsFromFile(
  662. context, fPath, CommonConstants.TYPE_LOCAL, autoParseMusicName
  663. );
  664. insertCount++
  665. try {
  666. // console.info('onecold scanDirectoryTask fPath = ' + fPath)
  667. // console.info('onecold scanDirectoryTask insertCount = ' + insertCount)
  668. taskpool.Task.sendData(fPath,insertCount);
  669. } catch (error) {
  670. let err = error as BusinessError;
  671. console.warn( `onecold sendData failed, code=${err.code}, message=${err.message}`);
  672. }
  673. table.insert(mediaItem, (id: number) => {
  674. },cover_api);
  675. }
  676. }
  677. }));
  678. }
  679. }