PlaylistTable.ets 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import { relationalStore } from '@kit.ArkData';
  2. import { Context } from '@kit.AbilityKit';
  3. import Logger from './Logger';
  4. import RdbUtils from './RdbUtils';
  5. import { Playlist, PlaylistSong } from '../../viewmodel/Playlist';
  6. /**
  7. * 歌单数据库操作类
  8. */
  9. export default class PlaylistTable {
  10. private context: Context;
  11. private rdbStore: relationalStore.RdbStore | null = null;
  12. private initPromise: Promise<void>;
  13. constructor(context: Context) {
  14. this.context = context;
  15. this.initPromise = this.initRdbStore();
  16. }
  17. /**
  18. * 确保数据库已初始化
  19. */
  20. private async ensureInitialized(): Promise<void> {
  21. await this.initPromise;
  22. }
  23. /**
  24. * 初始化数据库
  25. */
  26. private async initRdbStore(): Promise<void> {
  27. try {
  28. const config: relationalStore.StoreConfig = {
  29. name: 'PlaylistStore.db',
  30. securityLevel: relationalStore.SecurityLevel.S1 // 使用标准安全级别
  31. };
  32. this.rdbStore = await relationalStore.getRdbStore(this.context, config);
  33. // 创建表
  34. await this.createTables();
  35. Logger.info('heanup PlaylistTable', '数据库初始化成功');
  36. } catch (error) {
  37. Logger.error('heanup PlaylistTable', `初始化数据库失败: ${error.message}`);
  38. }
  39. }
  40. /**
  41. * 创建表
  42. */
  43. private async createTables(): Promise<void> {
  44. if (!this.rdbStore) {
  45. return;
  46. }
  47. try {
  48. // 创建歌单表
  49. await this.rdbStore.executeSql(RdbUtils.PLAYLIST_TABLE.sqlCreate);
  50. // 创建歌单歌曲关联表
  51. await this.rdbStore.executeSql(RdbUtils.PLAYLIST_SONG_TABLE.sqlCreate);
  52. Logger.info('heanup PlaylistTable', '数据库表创建成功');
  53. } catch (error) {
  54. Logger.error('heanup PlaylistTable', `创建表失败: ${error.message}`);
  55. }
  56. }
  57. /**
  58. * 生成唯一ID
  59. */
  60. private generateId(): string {
  61. return Date.now().toString() + Math.random().toString(36).substr(2, 9);
  62. }
  63. /**
  64. * 创建歌单
  65. */
  66. async createPlaylist(name: string, description?: string, coverPath?: string): Promise<boolean> {
  67. await this.ensureInitialized();
  68. if (!this.rdbStore) {
  69. Logger.error('heanup PlaylistTable', '数据库未初始化');
  70. return false;
  71. }
  72. try {
  73. const id = this.generateId();
  74. const now = new Date().toISOString();
  75. const sql = 'INSERT INTO playlistTable (id, name, coverPath, description, createTime, updateTime, songCount, sortOrder) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
  76. const params = [id, name, coverPath || null, description || null, now, now, 0, 0];
  77. await this.rdbStore.executeSql(sql, params);
  78. Logger.info('heanup PlaylistTable', `歌单创建成功: ${name}`);
  79. return true;
  80. } catch (error) {
  81. Logger.error('heanup PlaylistTable', `创建歌单失败: ${error.message}`);
  82. return false;
  83. }
  84. }
  85. /**
  86. * 删除歌单及其所有歌曲
  87. */
  88. async deletePlaylist(playlistId: string): Promise<boolean> {
  89. if (!this.rdbStore) {
  90. return false;
  91. }
  92. try {
  93. // 先删除歌单歌曲关联
  94. const deleteSongsSql = 'DELETE FROM playlistSongTable WHERE playlistId = ?';
  95. await this.rdbStore.executeSql(deleteSongsSql, [playlistId]);
  96. // 再删除歌单
  97. const deletePlaylistSql = 'DELETE FROM playlistTable WHERE id = ?';
  98. await this.rdbStore.executeSql(deletePlaylistSql, [playlistId]);
  99. Logger.info('heanup PlaylistTable', `歌单删除成功: ${playlistId}`);
  100. return true;
  101. } catch (error) {
  102. Logger.error('heanup PlaylistTable', `删除歌单失败: ${error.message}`);
  103. return false;
  104. }
  105. }
  106. /**
  107. * 更新歌单信息
  108. */
  109. async updatePlaylist(playlistId: string, name?: string, description?: string, coverPath?: string): Promise<boolean> {
  110. if (!this.rdbStore) {
  111. return false;
  112. }
  113. try {
  114. const updateTime = new Date().toISOString();
  115. const updates: string[] = [];
  116. const params: (string | number | null)[] = [];
  117. if (name !== undefined) {
  118. updates.push('name = ?');
  119. params.push(name);
  120. }
  121. if (description !== undefined) {
  122. updates.push('description = ?');
  123. params.push(description);
  124. }
  125. if (coverPath !== undefined) {
  126. updates.push('coverPath = ?');
  127. params.push(coverPath);
  128. }
  129. updates.push('updateTime = ?');
  130. params.push(updateTime);
  131. params.push(playlistId);
  132. const sql = `UPDATE playlistTable SET ${updates.join(', ')} WHERE id = ?`;
  133. await this.rdbStore.executeSql(sql, params);
  134. Logger.info('heanup PlaylistTable', `歌单更新成功: ${playlistId}`);
  135. return true;
  136. } catch (error) {
  137. Logger.error('heanup PlaylistTable', `更新歌单失败: ${error.message}`);
  138. return false;
  139. }
  140. }
  141. /**
  142. * 查询所有歌单
  143. */
  144. async queryAllPlaylists(): Promise<Playlist[]> {
  145. await this.ensureInitialized();
  146. if (!this.rdbStore) {
  147. Logger.error('heanup PlaylistTable', '数据库未初始化');
  148. return [];
  149. }
  150. try {
  151. const sql = 'SELECT * FROM playlistTable ORDER BY sortOrder ASC, createTime DESC';
  152. Logger.info('heanup PlaylistTable', '开始查询所有歌单');
  153. const resultSet = await this.rdbStore.querySql(sql);
  154. const playlists: Playlist[] = [];
  155. if (resultSet.goToFirstRow()) {
  156. do {
  157. const playlist = new Playlist(
  158. resultSet.getString(resultSet.getColumnIndex('id')),
  159. resultSet.getString(resultSet.getColumnIndex('name')),
  160. resultSet.getString(resultSet.getColumnIndex('createTime')),
  161. resultSet.getString(resultSet.getColumnIndex('updateTime')),
  162. resultSet.getLong(resultSet.getColumnIndex('songCount')),
  163. resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
  164. resultSet.getString(resultSet.getColumnIndex('coverPath')),
  165. resultSet.getString(resultSet.getColumnIndex('description'))
  166. );
  167. playlists.push(playlist);
  168. } while (resultSet.goToNextRow());
  169. }
  170. resultSet.close();
  171. Logger.info('heanup PlaylistTable', `查询到 ${playlists.length} 个歌单`);
  172. return playlists;
  173. } catch (error) {
  174. Logger.error('heanup PlaylistTable', `查询歌单失败: ${error.message}`);
  175. return [];
  176. }
  177. }
  178. /**
  179. * 根据ID查询歌单
  180. */
  181. async queryPlaylistById(playlistId: string): Promise<Playlist | null> {
  182. if (!this.rdbStore) {
  183. return null;
  184. }
  185. try {
  186. const sql = 'SELECT * FROM playlistTable WHERE id = ?';
  187. const resultSet = await this.rdbStore.querySql(sql, [playlistId]);
  188. if (resultSet.goToFirstRow()) {
  189. const playlist = new Playlist(
  190. resultSet.getString(resultSet.getColumnIndex('id')),
  191. resultSet.getString(resultSet.getColumnIndex('name')),
  192. resultSet.getString(resultSet.getColumnIndex('createTime')),
  193. resultSet.getString(resultSet.getColumnIndex('updateTime')),
  194. resultSet.getLong(resultSet.getColumnIndex('songCount')),
  195. resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
  196. resultSet.getString(resultSet.getColumnIndex('coverPath')),
  197. resultSet.getString(resultSet.getColumnIndex('description'))
  198. );
  199. resultSet.close();
  200. return playlist;
  201. }
  202. resultSet.close();
  203. return null;
  204. } catch (error) {
  205. Logger.error('heanup PlaylistTable', `查询歌单失败: ${error.message}`);
  206. return null;
  207. }
  208. }
  209. /**
  210. * 添加歌曲到歌单
  211. */
  212. async addSongToPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
  213. await this.ensureInitialized();
  214. if (!this.rdbStore) {
  215. Logger.error('heanup PlaylistTable', '数据库未初始化');
  216. return false;
  217. }
  218. try {
  219. Logger.info('heanup PlaylistTable', `开始添加歌曲到歌单: playlistId=${playlistId}, songFilePath=${songFilePath}`);
  220. // 检查歌曲是否已在歌单中
  221. const isInPlaylist = await this.isSongInPlaylist(playlistId, songFilePath);
  222. if (isInPlaylist) {
  223. Logger.info('heanup PlaylistTable', '歌曲已在歌单中');
  224. return false;
  225. }
  226. const id = this.generateId();
  227. const addTime = new Date().toISOString();
  228. const sql = 'INSERT INTO playlistSongTable (id, playlistId, songFilePath, addTime, sortOrder) VALUES (?, ?, ?, ?, ?)';
  229. const params = [id, playlistId, songFilePath, addTime, 0];
  230. Logger.info('heanup PlaylistTable', `执行SQL插入: ${sql}`);
  231. await this.rdbStore.executeSql(sql, params);
  232. Logger.info('heanup PlaylistTable', '歌曲插入成功');
  233. // 更新歌单歌曲数量
  234. Logger.info('heanup PlaylistTable', '开始更新歌单歌曲数量');
  235. await this.updatePlaylistSongCount(playlistId);
  236. Logger.info('heanup PlaylistTable', `歌曲添加到歌单成功: ${songFilePath}`);
  237. return true;
  238. } catch (error) {
  239. Logger.error('heanup PlaylistTable', `添加歌曲到歌单失败: ${error.message}`);
  240. return false;
  241. }
  242. }
  243. /**
  244. * 批量添加歌曲到歌单
  245. */
  246. async addSongsToPlaylist(playlistId: string, songFilePaths: string[]): Promise<boolean> {
  247. await this.ensureInitialized();
  248. if (!this.rdbStore) {
  249. Logger.error('heanup PlaylistTable', '数据库未初始化');
  250. return false;
  251. }
  252. if (!songFilePaths || songFilePaths.length === 0) {
  253. Logger.error('heanup PlaylistTable', '歌曲路径列表为空');
  254. return false;
  255. }
  256. try {
  257. Logger.info('heanup PlaylistTable', `开始批量添加歌曲到歌单: playlistId=${playlistId}, 歌曲数量=${songFilePaths.length}`);
  258. let successCount = 0;
  259. for (const songFilePath of songFilePaths) {
  260. try {
  261. // 检查歌曲是否已在歌单中
  262. const isInPlaylist = await this.isSongInPlaylist(playlistId, songFilePath);
  263. if (isInPlaylist) {
  264. Logger.info('heanup PlaylistTable', `歌曲已在歌单中,跳过: ${songFilePath}`);
  265. continue;
  266. }
  267. const id = this.generateId();
  268. const addTime = new Date().toISOString();
  269. const sql = 'INSERT INTO playlistSongTable (id, playlistId, songFilePath, addTime, sortOrder) VALUES (?, ?, ?, ?, ?)';
  270. const params = [id, playlistId, songFilePath, addTime, 0];
  271. await this.rdbStore.executeSql(sql, params);
  272. successCount++;
  273. Logger.info('heanup PlaylistTable', `成功添加歌曲: ${songFilePath}`);
  274. } catch (error) {
  275. Logger.error('heanup PlaylistTable', `添加歌曲失败: ${songFilePath}, 错误: ${error.message}`);
  276. }
  277. }
  278. // 更新歌单歌曲数量
  279. Logger.info('heanup PlaylistTable', '开始更新歌单歌曲数量');
  280. await this.updatePlaylistSongCount(playlistId);
  281. Logger.info('heanup PlaylistTable', `批量添加歌曲完成,成功添加 ${successCount} 首,共 ${songFilePaths.length} 首`);
  282. return successCount > 0;
  283. } catch (error) {
  284. Logger.error('heanup PlaylistTable', `批量添加歌曲到歌单失败: ${error.message}`);
  285. return false;
  286. }
  287. }
  288. /**
  289. * 从歌单中移除歌曲
  290. */
  291. async removeSongFromPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
  292. if (!this.rdbStore) {
  293. return false;
  294. }
  295. try {
  296. const sql = 'DELETE FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
  297. await this.rdbStore.executeSql(sql, [playlistId, songFilePath]);
  298. // 更新歌单歌曲数量
  299. await this.updatePlaylistSongCount(playlistId);
  300. Logger.info('heanup PlaylistTable', `歌曲从歌单移除成功: ${songFilePath}`);
  301. return true;
  302. } catch (error) {
  303. Logger.error('heanup PlaylistTable', `从歌单移除歌曲失败: ${error.message}`);
  304. return false;
  305. }
  306. }
  307. /**
  308. * 查询歌单中的歌曲
  309. */
  310. async queryPlaylistSongs(playlistId: string): Promise<PlaylistSong[]> {
  311. await this.ensureInitialized();
  312. if (!this.rdbStore) {
  313. Logger.error('heanup PlaylistTable', '数据库未初始化');
  314. return [];
  315. }
  316. try {
  317. const sql = 'SELECT * FROM playlistSongTable WHERE playlistId = ? ORDER BY sortOrder ASC, addTime ASC';
  318. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 开始查询歌单歌曲, playlistId=${playlistId}`);
  319. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: SQL=${sql}`);
  320. const resultSet = await this.rdbStore.querySql(sql, [playlistId]);
  321. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 查询执行完成, rowCount=${resultSet.rowCount}`);
  322. const songs: PlaylistSong[] = [];
  323. if (resultSet.goToFirstRow()) {
  324. Logger.info('heanup PlaylistTable', 'queryPlaylistSongs: 移动到第一行成功');
  325. do {
  326. const song = new PlaylistSong(
  327. resultSet.getString(resultSet.getColumnIndex('id')),
  328. resultSet.getString(resultSet.getColumnIndex('playlistId')),
  329. resultSet.getString(resultSet.getColumnIndex('songFilePath')),
  330. resultSet.getString(resultSet.getColumnIndex('addTime')),
  331. resultSet.getLong(resultSet.getColumnIndex('sortOrder'))
  332. );
  333. songs.push(song);
  334. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 添加歌曲, songFilePath=${song.songFilePath}`);
  335. } while (resultSet.goToNextRow());
  336. } else {
  337. Logger.info('heanup PlaylistTable', 'queryPlaylistSongs: 没有数据,无法移动到第一行');
  338. }
  339. resultSet.close();
  340. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 查询到 ${songs.length} 首歌曲`);
  341. return songs;
  342. } catch (error) {
  343. Logger.error('heanup PlaylistTable', `queryPlaylistSongs: 查询歌单歌曲失败: ${error.message}`);
  344. Logger.error('heanup PlaylistTable', `queryPlaylistSongs: 错误堆栈: ${error.stack || '无堆栈信息'}`);
  345. return [];
  346. }
  347. }
  348. /**
  349. * 检查歌曲是否在歌单中
  350. */
  351. async isSongInPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
  352. if (!this.rdbStore) {
  353. return false;
  354. }
  355. try {
  356. const sql = 'SELECT COUNT(*) as count FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
  357. const resultSet = await this.rdbStore.querySql(sql, [playlistId, songFilePath]);
  358. let isInPlaylist = false;
  359. if (resultSet.goToFirstRow()) {
  360. const count = resultSet.getLong(resultSet.getColumnIndex('count'));
  361. isInPlaylist = count > 0;
  362. }
  363. resultSet.close();
  364. return isInPlaylist;
  365. } catch (error) {
  366. Logger.error('heanup PlaylistTable', `检查歌曲是否在歌单中失败: ${error.message}`);
  367. return false;
  368. }
  369. }
  370. /**
  371. * 更新歌单歌曲数量
  372. */
  373. private async updatePlaylistSongCount(playlistId: string): Promise<void> {
  374. if (!this.rdbStore) {
  375. return;
  376. }
  377. try {
  378. const countSql = 'SELECT COUNT(*) as count FROM playlistSongTable WHERE playlistId = ?';
  379. const resultSet = await this.rdbStore.querySql(countSql, [playlistId]);
  380. let count = 0;
  381. if (resultSet.goToFirstRow()) {
  382. count = resultSet.getLong(resultSet.getColumnIndex('count'));
  383. }
  384. resultSet.close();
  385. const updateSql = 'UPDATE playlistTable SET songCount = ?, updateTime = ? WHERE id = ?';
  386. const updateTime = new Date().toISOString();
  387. await this.rdbStore.executeSql(updateSql, [count, updateTime, playlistId]);
  388. } catch (error) {
  389. Logger.error('heanup PlaylistTable', `更新歌单歌曲数量失败: ${error.message}`);
  390. }
  391. }
  392. /**
  393. * 更新歌单排序
  394. */
  395. async updatePlaylistSortOrder(playlistId: string, sortOrder: number): Promise<boolean> {
  396. if (!this.rdbStore) {
  397. return false;
  398. }
  399. try {
  400. const updateTime = new Date().toISOString();
  401. const sql = 'UPDATE playlistTable SET sortOrder = ?, updateTime = ? WHERE id = ?';
  402. await this.rdbStore.executeSql(sql, [sortOrder, updateTime, playlistId]);
  403. Logger.info('heanup PlaylistTable', `歌单排序更新成功: ${playlistId}`);
  404. return true;
  405. } catch (error) {
  406. Logger.error('heanup PlaylistTable', `更新歌单排序失败: ${error.message}`);
  407. return false;
  408. }
  409. }
  410. /**
  411. * 更新歌单歌曲排序
  412. */
  413. async updatePlaylistSongSortOrder(playlistId: string, songFilePath: string, sortOrder: number): Promise<boolean> {
  414. if (!this.rdbStore) {
  415. return false;
  416. }
  417. try {
  418. const sql = 'UPDATE playlistSongTable SET sortOrder = ? WHERE playlistId = ? AND songFilePath = ?';
  419. await this.rdbStore.executeSql(sql, [sortOrder, playlistId, songFilePath]);
  420. Logger.info('heanup PlaylistTable', `歌单歌曲排序更新成功: ${songFilePath}`);
  421. return true;
  422. } catch (error) {
  423. Logger.error('heanup PlaylistTable', `更新歌单歌曲排序失败: ${error.message}`);
  424. return false;
  425. }
  426. }
  427. }