MediaTable.ets 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. import relationalStore from '@ohos.data.relationalStore';
  2. import { LogUtil, StrUtil } from '@pura/harmony-utils';
  3. import { VideoItem } from '../../viewmodel/VideoItem';
  4. import { CommonConstants } from '../constants/CommonConstants';
  5. import Logger from './Logger';
  6. import RdbUtils from './RdbUtils';
  7. import { AudioQuality, Utility } from './Utility';
  8. /**
  9. * 数据库字段常量接口定义
  10. */
  11. interface DBColumnsInterface {
  12. ID: string;
  13. NAME: string;
  14. FILE_PATH: string;
  15. TYPE: string;
  16. VIDEO_SIZE: string;
  17. C_TIME: string;
  18. PARENT_PATH: string;
  19. IS_FAV: string;
  20. PIXEL_MAP_PATH: string;
  21. ARTIST: string;
  22. ALBUM: string;
  23. FILE_NAME: string;
  24. SIZE: string;
  25. DURATION: string;
  26. MIME_TYPE: string;
  27. TRACK_COUNT: string;
  28. SAMPLE_RATE: string;
  29. LAST_PLAYED_STR: string;
  30. PLAY_COUNT: string;
  31. LYRIC_CONTENT: string;
  32. }
  33. /**
  34. * 数据库字段常量,避免硬编码
  35. */
  36. const DB_COLUMNS: DBColumnsInterface = {
  37. ID: 'id',
  38. NAME: 'name',
  39. FILE_PATH: 'filePath',
  40. TYPE: 'mtype',
  41. VIDEO_SIZE: 'videoSize',
  42. C_TIME: 'cTime',
  43. PARENT_PATH: 'parentPath',
  44. IS_FAV: 'isFav',
  45. PIXEL_MAP_PATH: 'pixelMapPath',
  46. ARTIST: 'artist',
  47. ALBUM: 'album',
  48. FILE_NAME: 'fileName',
  49. SIZE: 'size',
  50. DURATION: 'duration',
  51. MIME_TYPE: 'mimeType',
  52. TRACK_COUNT: 'trackCount',
  53. SAMPLE_RATE: 'sampleRate',
  54. LAST_PLAYED_STR: 'lastPlayedStr',
  55. PLAY_COUNT: 'playCount',
  56. LYRIC_CONTENT: 'lyricContent'
  57. };
  58. export default class MediaTable {
  59. private accountTable = new RdbUtils(RdbUtils.MEDIA_TABLE.tableName, RdbUtils.MEDIA_TABLE.sqlCreate,
  60. RdbUtils.MEDIA_TABLE.columns);
  61. constructor(context:Context,callback: Function = () => {
  62. }) {
  63. this.accountTable.getRdbStore(context,callback);
  64. }
  65. getRdbStore(context:Context,callback: Function = () => {
  66. }) {
  67. this.accountTable.getRdbStore(context,callback);
  68. }
  69. insert(item: VideoItem, callback: Function,cover_api?:string) {
  70. const valueBucket: relationalStore.ValuesBucket = generateBucket(item);
  71. this.accountTable.insertData(valueBucket, callback,cover_api);
  72. }
  73. deleteData(item: VideoItem, callback: Function) {
  74. let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  75. predicates.equalTo('id', item.id);
  76. this.accountTable.deleteData(predicates, callback);
  77. }
  78. deleteDataFilePath(filePath: string, callback: Function) {
  79. let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  80. predicates.equalTo('filePath', filePath);
  81. this.accountTable.deleteData(predicates, callback);
  82. }
  83. deleteDataForParentPath(parentPath:string, callback: Function) {
  84. let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  85. predicates.equalTo('parentPath', parentPath);
  86. this.accountTable.deleteData(predicates, callback);
  87. }
  88. //更新音乐封面地址
  89. public updatePixelMapPath(filePath: string, newPixelMapPath: string, callback: Function) {
  90. // Step 1: 构建查询条件验证文件存在性
  91. const queryPredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  92. queryPredicates.equalTo('filePath', filePath);
  93. // Step 2: 执行存在性验证
  94. this.accountTable.query(queryPredicates, (resultSet: relationalStore.ResultSet) => {
  95. if (resultSet.rowCount === 0) {
  96. callback(false, 'Error: Target file not found in database');
  97. resultSet.close();
  98. return;
  99. }
  100. resultSet.close();
  101. // Step 3: 构建更新条件与数据
  102. const updatePredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  103. updatePredicates.equalTo('filePath', filePath);
  104. const valueBucket: relationalStore.ValuesBucket = {
  105. pixelMapPath: newPixelMapPath
  106. };
  107. // Step 4: 执行原子化更新操作
  108. this.accountTable.updateData(updatePredicates, valueBucket, (success: boolean) => {
  109. callback(success, success ? null : 'Database update operation failed');
  110. });
  111. });
  112. }
  113. updateData(item: VideoItem, callback: Function) {
  114. const valueBucket: relationalStore.ValuesBucket = generateBucket(item);
  115. let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  116. predicates.equalTo('id', item.id);
  117. this.accountTable.updateData(predicates, valueBucket, callback);
  118. }
  119. //编辑歌曲的信息更新数据库
  120. public updateMediaInfo(filePath: string, title: string, artist: string, album: string,
  121. lyricContent:string,year:string,genre:string,track:string,
  122. ALBUMARTIST:string,COMPOSER:string,LYRICIST:string,COMMENT:string,disc:string,
  123. callback: Function) {
  124. if (!callback || typeof callback !== 'function') {
  125. Logger.info(RdbUtils.RDB_TAG, 'updateMediaInfo() has no valid callback!');
  126. return;
  127. }
  128. if (!this.accountTable) {
  129. Logger.error(RdbUtils.RDB_TAG, 'RdbStore is not initialized.');
  130. callback(false);
  131. return;
  132. }
  133. // Step 1: Create a predicate to find the record by filePath
  134. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  135. predicates.equalTo('filePath', filePath);
  136. // Step 2: Query the database to check if the record exists
  137. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  138. if (resultSet.rowCount === 0) {
  139. Logger.info(RdbUtils.RDB_TAG, `No record found with filePath ${filePath}.`);
  140. callback(false);
  141. resultSet.close();
  142. return;
  143. }
  144. // Step 3: Prepare the values to update
  145. const valuesToUpdate: relationalStore.ValuesBucket = {};
  146. if (title !== '') {
  147. valuesToUpdate.name = title;
  148. }
  149. if (artist !== '') {
  150. valuesToUpdate.artist = artist;
  151. }
  152. if (album !== '') {
  153. valuesToUpdate.album = album;
  154. }
  155. if (lyricContent !== '') {
  156. valuesToUpdate.lyricContent = lyricContent;
  157. }
  158. if (year !== '') {
  159. valuesToUpdate.year = year;
  160. }
  161. if (genre !== '') {
  162. valuesToUpdate.genre = genre;
  163. }
  164. if (track !== '') {
  165. valuesToUpdate.track = track;
  166. }
  167. if (ALBUMARTIST !== '') {
  168. valuesToUpdate.ALBUMARTIST = ALBUMARTIST;
  169. }
  170. if (COMPOSER !== '') {
  171. valuesToUpdate.COMPOSER = COMPOSER;
  172. }
  173. if (LYRICIST !== '') {
  174. valuesToUpdate.LYRICIST = LYRICIST;
  175. }
  176. if (COMMENT !== '') {
  177. valuesToUpdate.COMMENT = COMMENT;
  178. }
  179. if (disc !== '') {
  180. valuesToUpdate.disc = disc;
  181. }
  182. resultSet.close();
  183. // Step 4: Update the record if there are values to update
  184. if (Object.keys(valuesToUpdate).length > 0) {
  185. this.accountTable.updateData(predicates, valuesToUpdate, (success: boolean) => {
  186. callback(success, success ? null : 'Database update operation failed');
  187. });
  188. } else {
  189. Logger.info(RdbUtils.RDB_TAG, 'No fields to update.');
  190. callback(false);
  191. }
  192. });
  193. }
  194. //更新重命名数据操作
  195. public updateRename(newName: string, oldPath: string, newPath: string, callback: Function) {
  196. // Step 1: 查询原始记录
  197. const queryPredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  198. queryPredicates.equalTo('filePath', oldPath);
  199. this.accountTable.query(queryPredicates, (resultSet: relationalStore.ResultSet) => {
  200. if (resultSet.rowCount === 0) {
  201. callback(false, 'Error: File not found');
  202. return;
  203. }
  204. resultSet.goToFirstRow();
  205. // Step 3: 构建更新数据
  206. const currentName = resultSet.getString(resultSet.getColumnIndex('name'));
  207. const currentFileName = resultSet.getString(resultSet.getColumnIndex('fileName'));
  208. let obj: relationalStore.ValuesBucket = {};
  209. obj.id = newPath
  210. obj.filePath = newPath;
  211. if (currentName === currentFileName) {
  212. obj.name = newName;
  213. obj.fileName = newName;
  214. } else {
  215. obj.fileName = newName;
  216. }
  217. obj.mtype = resultSet.getDouble(resultSet.getColumnIndex('mtype'));
  218. obj.videoSize = resultSet.getDouble(resultSet.getColumnIndex('videoSize'));
  219. obj.cTime = resultSet.getString(resultSet.getColumnIndex('cTime'));
  220. obj.parentPath = resultSet.getString(resultSet.getColumnIndex('parentPath'));
  221. // obj.pixelMapToString = resultSet.getString(resultSet.getColumnIndex('pixelMapToString'));
  222. obj.artist = resultSet.getString(resultSet.getColumnIndex('artist'));
  223. obj.album = resultSet.getString(resultSet.getColumnIndex('album'));
  224. obj.isFav = resultSet.getDouble(resultSet.getColumnIndex('isFav'));
  225. obj.pixelMapPath = resultSet.getString(resultSet.getColumnIndex('pixelMapPath'));
  226. obj.duration = resultSet.getString(resultSet.getColumnIndex('duration'));
  227. obj.mimeType = resultSet.getString(resultSet.getColumnIndex('mimeType'));
  228. obj.trackCount = resultSet.getString(resultSet.getColumnIndex('trackCount'));
  229. obj.sampleRate = resultSet.getString(resultSet.getColumnIndex('sampleRate'));
  230. obj.lastPlayedStr = resultSet.getString(resultSet.getColumnIndex('lastPlayedStr'));
  231. obj.playCount = resultSet.getDouble(resultSet.getColumnIndex('playCount'));
  232. obj.lyricContent = resultSet.getString(resultSet.getColumnIndex('lyricContent'));
  233. obj.md5Str = resultSet.getString(resultSet.getColumnIndex('md5Str'));
  234. obj.extra_json = resultSet.getString(resultSet.getColumnIndex('extra_json'));
  235. obj.pyStr = resultSet.getString(resultSet.getColumnIndex('pyStr'));
  236. obj.bit_rate = resultSet.getString(resultSet.getColumnIndex('bit_rate'));
  237. obj.probe_score = resultSet.getDouble(resultSet.getColumnIndex('probe_score'));
  238. obj.year = resultSet.getString(resultSet.getColumnIndex('year'));
  239. obj.nb_streams = resultSet.getDouble(resultSet.getColumnIndex('nb_streams'));
  240. obj.nb_programs = resultSet.getDouble(resultSet.getColumnIndex('nb_programs'));
  241. obj.genre = resultSet.getString(resultSet.getColumnIndex('genre'));
  242. obj.track = resultSet.getString(resultSet.getColumnIndex('track'));
  243. obj.bits_per_raw_sample = resultSet.getString(resultSet.getColumnIndex('bits_per_raw_sample'));
  244. obj.channels = resultSet.getString(resultSet.getColumnIndex('channels'));
  245. obj.channel_layout = resultSet.getString(resultSet.getColumnIndex('channel_layout'));
  246. obj.start_time = resultSet.getString(resultSet.getColumnIndex('start_time'));
  247. obj.ALBUMARTIST = resultSet.getString(resultSet.getColumnIndex('ALBUMARTIST'));
  248. obj.COMPOSER = resultSet.getString(resultSet.getColumnIndex('COMPOSER'));
  249. obj.LYRICIST = resultSet.getString(resultSet.getColumnIndex('LYRICIST'));
  250. obj.COMMENT = resultSet.getString(resultSet.getColumnIndex('COMMENT'));
  251. obj.disc = resultSet.getString(resultSet.getColumnIndex('disc'));
  252. const valueBucket: relationalStore.ValuesBucket = obj
  253. // Step 4: 执行更新
  254. const updatePredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  255. updatePredicates.equalTo('filePath', oldPath);
  256. this.accountTable.updateData(updatePredicates, valueBucket, (success: boolean) => {
  257. callback(success, success ? null : 'Update failed');
  258. });
  259. resultSet.close()
  260. });
  261. }
  262. // 根据isFav查询数据
  263. public queryByisFav(isFav: number, callback: (result: VideoItem[]) => void) {
  264. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  265. predicates.equalTo('isFav', isFav);
  266. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  267. const result = this.parseResultSetToVideoItems(resultSet);
  268. callback(result);
  269. });
  270. }
  271. // 根据filePath更新isFav的值
  272. public updateIsFavByFilePath(filePath: string, isFav: number, callback: (success: boolean, error?: string) => void) {
  273. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  274. predicates.equalTo('filePath', filePath);
  275. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  276. if (resultSet.rowCount === 0) {
  277. callback(false, 'Error: File not found');
  278. resultSet.close();
  279. return;
  280. }
  281. resultSet.close();
  282. const valueBucket: relationalStore.ValuesBucket = { isFav: isFav };
  283. this.accountTable.updateData(predicates, valueBucket, (success: boolean) => {
  284. callback(success, success ? '' : 'Update failed');
  285. });
  286. });
  287. }
  288. // 查询全部,或者某个id(查询的字段,回调,是否查询全部)
  289. query(id: number, callback: Function, isAll: boolean = true) {
  290. let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  291. if (!isAll) {
  292. predicates.equalTo('id', id);
  293. }
  294. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  295. let count: number = resultSet.rowCount;
  296. if (count === 0 || typeof count === 'string') {
  297. console.log(`${RdbUtils.RDB_TAG}` + 'Query no results!');
  298. callback([]);
  299. } else {
  300. const result = this.parseResultSetToVideoItems(resultSet);
  301. callback(result);
  302. }
  303. });
  304. }
  305. // 新增方法:根据parentPath查询数据
  306. public queryByParentPath(path: string, callback: (result: VideoItem[]) => void) {
  307. try {
  308. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  309. predicates.equalTo('parentPath', path);
  310. LogUtil.info('onecold parentPath = '+path)
  311. // 2. 执行查询并处理结果
  312. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  313. // 3. 复用已有的解析逻辑
  314. const result = this.parseResultSetToVideoItems(resultSet);
  315. callback(result);
  316. });
  317. }catch (err) {
  318. Logger.error(` onecold testtag queryByParentPath: ${err.code} - ${err.message}`);
  319. }
  320. // 1. 构建查询条件
  321. }
  322. // 查询所有艺术家及其歌曲(返回Map结构:artist -> VideoItem[])
  323. public queryArtistsWithSongs(callback: (result: Map<string, VideoItem[]>) => void) {
  324. // 1. 查询去重的艺术家列表(非空)
  325. const artistPredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  326. artistPredicates.isNotNull('artist').distinct();
  327. this.accountTable.query(artistPredicates, (resultSet: relationalStore.ResultSet) => {
  328. const artists: string[] = this.parseDistinctColumn(resultSet, 'artist');
  329. // 2. 遍历每个艺术家,查询其歌曲
  330. const resultMap = new Map<string, VideoItem[]>();
  331. let processedCount = 0;
  332. if (artists.length === 0) {
  333. callback(resultMap);
  334. return;
  335. }
  336. artists.forEach(artist => {
  337. const songPredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  338. songPredicates.equalTo('artist', artist);
  339. this.accountTable.query(songPredicates, (songResultSet: relationalStore.ResultSet) => {
  340. const songs = this.parseResultSetToVideoItems(songResultSet);
  341. resultMap.set(artist, songs);
  342. processedCount++;
  343. // 3. 全部查询完成后回调
  344. if (processedCount === artists.length) {
  345. callback(resultMap);
  346. }
  347. });
  348. });
  349. });
  350. }
  351. // 查询所有专辑及其歌曲(返回Map结构:album -> VideoItem[])
  352. public queryAlbumsWithSongs(callback: (result: Map<string, VideoItem[]>) => void) {
  353. // 1. 查询去重的专辑列表(非空)
  354. const albumPredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  355. albumPredicates.isNotNull('album').distinct();
  356. this.accountTable.query(albumPredicates, (resultSet: relationalStore.ResultSet) => {
  357. const albums: string[] = this.parseDistinctColumn(resultSet, 'album');
  358. // 2. 遍历每个专辑,查询其歌曲
  359. const resultMap = new Map<string, VideoItem[]>();
  360. let processedCount = 0;
  361. if (albums.length === 0) {
  362. callback(resultMap);
  363. return;
  364. }
  365. albums.forEach(album => {
  366. const songPredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  367. songPredicates.equalTo('album', album);
  368. this.accountTable.query(songPredicates, (songResultSet: relationalStore.ResultSet) => {
  369. const songs = this.parseResultSetToVideoItems(songResultSet);
  370. resultMap.set(album, songs);
  371. processedCount++;
  372. // 3. 全部查询完成后回调
  373. if (processedCount === albums.length) {
  374. callback(resultMap);
  375. }
  376. });
  377. });
  378. });
  379. }
  380. // 解析去重列数据(如artist/album)
  381. private parseDistinctColumn(resultSet: relationalStore.ResultSet, columnName: string): string[] {
  382. const uniqueValues = new Set<string>(); // 使用Set特性自动去重
  383. if (resultSet.rowCount > 0) {
  384. resultSet.goToFirstRow();
  385. for (let i = 0; i < resultSet.rowCount; i++) {
  386. const value = resultSet.getString(resultSet.getColumnIndex(columnName))?.trim(); // 处理空格
  387. if (value) { // 过滤空值
  388. uniqueValues.add(value);
  389. }
  390. if (i < resultSet.rowCount - 1) { // 避免最后一行越界
  391. resultSet.goToNextRow();
  392. }
  393. }
  394. }
  395. resultSet.close();
  396. return Array.from(uniqueValues); // Set转数组
  397. }
  398. // 将ResultSet解析为VideoItem数组(复用原有逻辑)
  399. private parseResultSetToVideoItems(resultSet: relationalStore.ResultSet): VideoItem[] {
  400. const items: VideoItem[] = [];
  401. try {
  402. // 检查结果集是否有效
  403. if (resultSet && resultSet.rowCount > 0) {
  404. while (resultSet.goToNextRow()) {
  405. const item = this.buildVideoItem(resultSet);
  406. items.push(item);
  407. }
  408. }
  409. } catch (err) {
  410. Logger.error(`解析结果集出错: ${err.message}`);
  411. } finally {
  412. // 确保结果集被关闭
  413. if (resultSet) {
  414. resultSet.close();
  415. }
  416. }
  417. return items;
  418. }
  419. // 根据filePath更新lastPlayedStr的值同时playCount值加1
  420. public updateLastPlayedStrByFilePath(filePath: string, lastPlayedStr: string, callback: (success: boolean, error?: string) => void) {
  421. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  422. predicates.equalTo('filePath', filePath);
  423. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  424. if (resultSet.rowCount === 0) {
  425. callback(false, 'Error: File not found');
  426. resultSet.close();
  427. return;
  428. }
  429. // 获取当前的 playCount 值
  430. let currentPlayCount = 0;
  431. if (resultSet.goToFirstRow()) {
  432. currentPlayCount = resultSet.getLong(resultSet.getColumnIndex('playCount'));
  433. }
  434. resultSet.close();
  435. // 计算新的 playCount 值
  436. const newPlayCount = currentPlayCount + 1;
  437. // 准备要更新的值
  438. const valueBucket: relationalStore.ValuesBucket = {
  439. lastPlayedStr: lastPlayedStr,
  440. playCount: newPlayCount
  441. };
  442. // 更新数据
  443. this.accountTable.updateData(predicates, valueBucket, (success: boolean) => {
  444. callback(success, success ? '' : 'Update failed');
  445. });
  446. });
  447. }
  448. // 根据最近播放时间查询指定数量的记录
  449. public queryRecentPlayedRecords(count: number, callback: (result: VideoItem[]) => void) {
  450. try {
  451. // 1. 构建查询条件:按lastPlayedStr降序排列,限制返回条数,且lastPlayedStr不为空
  452. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  453. // 添加筛选条件,确保lastPlayedStr不为空
  454. predicates.isNotNull('lastPlayedStr');
  455. predicates.notEqualTo('lastPlayedStr', ''); // 排除空字符串
  456. predicates.notEqualTo('mtype', CommonConstants.TYPE_LOCK);
  457. // 使用 orderByDesc 方法进行降序排序
  458. predicates.orderByDesc('lastPlayedStr');
  459. // 使用 limit 方法限制返回的记录数量
  460. predicates.limitAs(count);
  461. // 2. 执行查询并处理结果
  462. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  463. // 3. 复用已有的解析逻辑
  464. const result = this.parseResultSetToVideoItems(resultSet);
  465. callback(result);
  466. });
  467. } catch (err) {
  468. Logger.error(`queryRecentPlayedRecords error: ${err.code} - ${err.message}`);
  469. callback([]);
  470. }
  471. }
  472. // 清空播放历史记录
  473. public clearPlayHistory(callback: (success: boolean, error?: string) => void) {
  474. // 1. 构建查询条件:筛选lastPlayedStr非空的记录
  475. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  476. predicates.isNotNull('lastPlayedStr');
  477. // 2. 准备更新数据:将lastPlayedStr设为空字符串
  478. const valueBucket: relationalStore.ValuesBucket = {
  479. lastPlayedStr: ''
  480. };
  481. // 3. 执行批量更新操作
  482. this.accountTable.updateData(predicates, valueBucket, (success: boolean) => {
  483. // 将 null 替换为 undefined
  484. callback(success, success ? 'Clear play history success' : 'Clear play history operation failed');
  485. });
  486. }
  487. private buildVideoItem(rs: relationalStore.ResultSet): VideoItem {
  488. // 添加空值保护
  489. const safeGet = (col: string) => {
  490. const index = rs.getColumnIndex(col);
  491. return index >= 0 ? rs.getString(index) || '' : '';
  492. };
  493. const safeGetNumber = (col: string) => {
  494. const index = rs.getColumnIndex(col);
  495. return index >= 0 ? rs.getDouble(index) || 0 : 0;
  496. };
  497. let item = new VideoItem(
  498. safeGet(DB_COLUMNS.NAME),
  499. safeGet(DB_COLUMNS.ID),
  500. safeGet(DB_COLUMNS.FILE_PATH),
  501. safeGetNumber(DB_COLUMNS.TYPE),
  502. safeGetNumber(DB_COLUMNS.VIDEO_SIZE),
  503. safeGet(DB_COLUMNS.C_TIME),
  504. undefined,
  505. safeGet(DB_COLUMNS.SIZE),
  506. safeGet(DB_COLUMNS.PIXEL_MAP_PATH),
  507. safeGet(DB_COLUMNS.ARTIST),
  508. safeGet(DB_COLUMNS.ALBUM),
  509. safeGet(DB_COLUMNS.FILE_NAME)
  510. );
  511. // 设置额外属性,添加安全检查
  512. item.isFav = safeGetNumber(DB_COLUMNS.IS_FAV);
  513. item.duration = safeGet(DB_COLUMNS.DURATION);
  514. item.mimeType = safeGet(DB_COLUMNS.MIME_TYPE);
  515. item.trackCount = safeGet(DB_COLUMNS.TRACK_COUNT);
  516. item.sampleRate = safeGet(DB_COLUMNS.SAMPLE_RATE);
  517. item.lastPlayedStr = safeGet(DB_COLUMNS.LAST_PLAYED_STR);
  518. item.playCount = safeGetNumber(DB_COLUMNS.PLAY_COUNT);
  519. item.lyricContent = safeGet(DB_COLUMNS.LYRIC_CONTENT);
  520. item.md5Str = safeGet('md5Str');
  521. item.extra_json = safeGet('extra_json');
  522. item.pyStr = safeGet('pyStr');
  523. item.bit_rate = safeGet('bit_rate');
  524. item.probe_score = safeGetNumber('probe_score');
  525. item.year = safeGet('year');
  526. item.nb_streams = safeGetNumber('nb_streams');
  527. item.nb_programs = safeGetNumber('nb_programs');
  528. item.genre = safeGet('genre');
  529. item.track = safeGet('track');
  530. item.bits_per_raw_sample = safeGet('bits_per_raw_sample');
  531. item.channels = safeGet('channels');
  532. item.channel_layout = safeGet('channel_layout');
  533. item.start_time = safeGet('start_time');
  534. item.ALBUMARTIST = safeGet('ALBUMARTIST');
  535. item.COMPOSER = safeGet('COMPOSER');
  536. item.LYRICIST = safeGet('LYRICIST');
  537. item.COMMENT = safeGet('COMMENT');
  538. item.disc = safeGet('disc');
  539. return item;
  540. }
  541. /**
  542. * 查询播放次数大于0的记录,并按播放次数降序排列
  543. * @param limitCount 限制返回的记录数量,0表示返回全部
  544. * @param callback 回调函数,返回VideoItem数组
  545. */
  546. public queryByPlayCountDesc(limitCount: number = 0, callback: (result: VideoItem[]) => void): void {
  547. try {
  548. // 1. 构建查询条件:playCount > 0,按playCount降序排列
  549. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  550. predicates.greaterThan('playCount', 0); // 播放次数大于0
  551. predicates.orderByDesc('playCount'); // 按播放次数降序排列
  552. // 只有当limitCount大于0时才设置限制
  553. if (limitCount > 0) {
  554. predicates.limitAs(limitCount);
  555. }
  556. // 2. 执行查询
  557. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  558. // 3. 解析结果集
  559. const result = this.parseResultSetToVideoItems(resultSet);
  560. callback(result);
  561. });
  562. } catch (err) {
  563. Logger.error(`queryByPlayCountDesc error: ${err.code} - ${err.message}`);
  564. callback([]); // 发生错误时返回空数组
  565. }
  566. }
  567. public countSongsByQualityOptimized(callback: (result: Record<AudioQuality, number>) => void): void {
  568. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  569. predicates.in('md5Str', [AudioQuality.LQ, AudioQuality.SQ, AudioQuality.HQ, AudioQuality.HIRES, AudioQuality.HR]);
  570. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  571. const result: Record<AudioQuality, number> = {
  572. [AudioQuality.LQ]: 0,
  573. [AudioQuality.SQ]: 0,
  574. [AudioQuality.HQ]: 0,
  575. [AudioQuality.HIRES]: 0 , // 只保留HR,HIRES的统计将合并到这里
  576. [AudioQuality.HR]: 0 // 只保留HR,HIRES的统计将合并到这里
  577. };
  578. if (resultSet.rowCount > 0) {
  579. resultSet.goToFirstRow();
  580. do {
  581. const quality = resultSet.getString(resultSet.getColumnIndex('md5Str')) as AudioQuality;
  582. if (quality === AudioQuality.LQ ||
  583. quality === AudioQuality.SQ ||
  584. quality === AudioQuality.HQ ||
  585. quality === AudioQuality.HIRES || // HIRES的统计将被合并到HR
  586. quality === AudioQuality.HR) {
  587. // 如果质量是HIRES或HR,都统计到HR中
  588. const targetKey = (quality === AudioQuality.HIRES || quality === AudioQuality.HR)
  589. ? AudioQuality.HR
  590. : quality;
  591. result[targetKey]++;
  592. }
  593. } while (resultSet.goToNextRow());
  594. }
  595. resultSet.close();
  596. callback(result);
  597. });
  598. }
  599. /**
  600. * Get lyric content by file path (Promise version)
  601. * @param filePath The file path to query
  602. * @returns Promise that resolves with the lyric content (string) or null if not found
  603. */
  604. public getLyricContentByFilePath(filePath: string): Promise<string | null> {
  605. return new Promise((resolve, reject) => {
  606. // Create query predicates
  607. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  608. predicates.equalTo(DB_COLUMNS.FILE_PATH, filePath);
  609. // Execute the query
  610. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  611. try {
  612. if (resultSet.rowCount === 0) {
  613. Logger.info(RdbUtils.RDB_TAG, `No record found for filePath: ${filePath}`);
  614. resolve(null);
  615. return;
  616. }
  617. // Get the first row
  618. resultSet.goToFirstRow();
  619. // Get the column index safely
  620. const columnIndex = resultSet.getColumnIndex(DB_COLUMNS.LYRIC_CONTENT);
  621. if (columnIndex < 0) {
  622. Logger.error(RdbUtils.RDB_TAG, `Column ${DB_COLUMNS.LYRIC_CONTENT} not found`);
  623. resolve(null);
  624. return;
  625. }
  626. // Get the lyric content
  627. const lyricContent = resultSet.getString(columnIndex);
  628. resolve(lyricContent || null);
  629. } catch (err) {
  630. Logger.error(RdbUtils.RDB_TAG, `Error getting lyric content: ${err.message}`);
  631. reject(err);
  632. } finally {
  633. // Ensure the result set is closed
  634. resultSet.close();
  635. }
  636. });
  637. });
  638. }
  639. /**
  640. * Check if a record exists in the database by filePath
  641. * @param filePath The file path to check
  642. * @returns Promise that resolves to true if record exists, false otherwise
  643. */
  644. public isRecordExists(filePath: string): Promise<boolean> {
  645. return new Promise((resolve, reject) => {
  646. try {
  647. // Create query predicates
  648. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  649. predicates.equalTo(DB_COLUMNS.FILE_PATH, filePath);
  650. // Execute the query
  651. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  652. try {
  653. // If rowCount > 0, record exists
  654. resolve(resultSet.rowCount > 0);
  655. } catch (err) {
  656. Logger.error(RdbUtils.RDB_TAG, `Error checking record existence: ${err.message}`);
  657. reject(err);
  658. } finally {
  659. // Ensure the result set is closed
  660. resultSet.close();
  661. }
  662. });
  663. } catch (err) {
  664. Logger.error(RdbUtils.RDB_TAG, `Error creating query: ${err.message}`);
  665. reject(err);
  666. }
  667. });
  668. }
  669. /**
  670. * Query a VideoItem by file path
  671. * @param filePath The file path to query
  672. * @returns Promise that resolves with the VideoItem or null if not found
  673. */
  674. public queryVideoByFilePath(filePath: string): Promise<VideoItem | null> {
  675. return new Promise((resolve, reject) => {
  676. try {
  677. // Create query predicates
  678. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  679. predicates.equalTo(DB_COLUMNS.FILE_PATH, filePath);
  680. // Execute the query
  681. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  682. try {
  683. if (resultSet.rowCount === 0) {
  684. Logger.info(RdbUtils.RDB_TAG, `No record found for filePath: ${filePath}`);
  685. resolve(null);
  686. return;
  687. }
  688. // Get the first row
  689. if (resultSet.goToFirstRow()) {
  690. const videoItem = this.buildVideoItem(resultSet);
  691. resolve(videoItem);
  692. } else {
  693. resolve(null);
  694. }
  695. } catch (err) {
  696. Logger.error(RdbUtils.RDB_TAG, `Error querying video by filePath: ${err.message}`);
  697. reject(err);
  698. } finally {
  699. // Ensure the result set is closed
  700. resultSet.close();
  701. }
  702. });
  703. } catch (err) {
  704. Logger.error(RdbUtils.RDB_TAG, `Error creating query: ${err.message}`);
  705. reject(err);
  706. }
  707. });
  708. }
  709. /**
  710. * Query all video items
  711. * @returns Promise that resolves with an array of VideoItem objects
  712. */
  713. public queryAllVideos(): Promise<VideoItem[]> {
  714. return new Promise((resolve, reject) => {
  715. try {
  716. Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 开始查询所有音乐文件');
  717. // Create query predicates for all records
  718. const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
  719. // Only query music files (type = 0)
  720. predicates.equalTo(DB_COLUMNS.TYPE, 0);
  721. // Order by name
  722. predicates.orderByAsc(DB_COLUMNS.NAME);
  723. Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 创建查询条件完成');
  724. // Execute the query
  725. this.accountTable.query(predicates, (resultSet: relationalStore.ResultSet) => {
  726. try {
  727. Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 查询回调执行');
  728. if (resultSet.rowCount === 0) {
  729. Logger.info(RdbUtils.RDB_TAG, 'queryAllVideos: 没有找到音乐文件');
  730. resolve([]);
  731. return;
  732. }
  733. Logger.info(RdbUtils.RDB_TAG, `queryAllVideos: 找到 ${resultSet.rowCount} 条记录`);
  734. const items: VideoItem[] = [];
  735. // Go to first row
  736. if (resultSet.goToFirstRow()) {
  737. do {
  738. const item = this.buildVideoItem(resultSet);
  739. items.push(item);
  740. } while (resultSet.goToNextRow());
  741. }
  742. Logger.info(RdbUtils.RDB_TAG, `queryAllVideos: 解析完成,返回 ${items.length} 个项目`);
  743. resolve(items);
  744. } catch (err) {
  745. Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 解析结果集出错: ${err.message}`);
  746. Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 错误堆栈: ${err.stack || '无堆栈信息'}`);
  747. reject(err);
  748. }
  749. // 注意:不在这里关闭 resultSet,因为 RdbUtils.query 会在回调函数执行后关闭它
  750. });
  751. } catch (err) {
  752. Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 创建查询出错: ${err.message}`);
  753. Logger.error(RdbUtils.RDB_TAG, `queryAllVideos: 错误堆栈: ${err.stack || '无堆栈信息'}`);
  754. reject(err);
  755. }
  756. });
  757. }
  758. }
  759. function generateBucket(item: VideoItem): relationalStore.ValuesBucket {
  760. let obj: relationalStore.ValuesBucket = {};
  761. obj.id = item.id
  762. obj.name = item.name;
  763. obj.filePath = item.filePath;
  764. obj.mtype = item.type;
  765. obj.videoSize = item.videoSize;
  766. obj.cTime = item.cTime;
  767. obj.parentPath = item.parentPath;
  768. obj.isFav = item.isFav;
  769. // if(item.pixelMapToString){
  770. // obj.pixelMapToString = item.pixelMapToString;
  771. // }
  772. if(item.artist){
  773. obj.artist = item.artist;
  774. }
  775. if(item.album){
  776. obj.album = item.album;
  777. }
  778. if(item.fileName){
  779. obj.fileName = item.fileName;
  780. }
  781. if(item.size){
  782. obj.size = item.size;
  783. }
  784. if(item.pixelMapPath){
  785. obj.pixelMapPath = item.pixelMapPath;
  786. }
  787. if(item.duration){
  788. obj.duration = item.duration;
  789. }
  790. if(item.mimeType){
  791. obj.mimeType = item.mimeType;
  792. }
  793. if(item.trackCount){
  794. obj.trackCount = item.trackCount;
  795. }
  796. if(item.sampleRate){
  797. obj.sampleRate = item.sampleRate;
  798. }
  799. if(item.lastPlayedStr){
  800. obj.lastPlayedStr = item.lastPlayedStr;
  801. }
  802. if(item.playCount){
  803. obj.playCount = item.playCount;
  804. }
  805. if(item.lyricContent){
  806. obj.lyricContent = item.lyricContent;
  807. }
  808. if(item.md5Str){
  809. obj.md5Str = item.md5Str;
  810. }
  811. if(item.extra_json){
  812. obj.extra_json = item.extra_json;
  813. }
  814. if(item.pyStr){
  815. obj.pyStr = item.pyStr;
  816. }
  817. if(item.bit_rate){
  818. obj.bit_rate = item.bit_rate;
  819. }
  820. if(item.probe_score){
  821. obj.probe_score = item.probe_score;
  822. }
  823. if(item.year){
  824. obj.year = item.year;
  825. }
  826. if(item.nb_streams){
  827. obj.nb_streams = item.nb_streams;
  828. }
  829. if(item.nb_programs){
  830. obj.nb_programs = item.nb_programs;
  831. }
  832. if(item.genre){
  833. obj.genre = item.genre;
  834. }
  835. if(item.track){
  836. obj.track = item.track;
  837. }
  838. if(item.bits_per_raw_sample){
  839. obj.bits_per_raw_sample = item.bits_per_raw_sample;
  840. }
  841. if(item.channels){
  842. obj.channels = item.channels;
  843. }
  844. if(item.channel_layout){
  845. obj.channel_layout = item.channel_layout;
  846. }
  847. if(item.start_time){
  848. obj.start_time = item.start_time;
  849. }
  850. if(item.ALBUMARTIST){
  851. obj.ALBUMARTIST = item.ALBUMARTIST;
  852. }
  853. if(item.COMPOSER){
  854. obj.COMPOSER = item.COMPOSER;
  855. }
  856. if(item.LYRICIST){
  857. obj.LYRICIST = item.LYRICIST;
  858. }
  859. if(item.COMMENT){
  860. obj.COMMENT = item.COMMENT;
  861. }
  862. if(item.disc){
  863. obj.disc = item.disc;
  864. }
  865. return obj;
  866. }