PlaylistTable.ets 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  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. import {
  7. PlaylistBackupData,
  8. PlaylistBackupItem,
  9. PlaylistSongBackupItem,
  10. ImportResult,
  11. ConflictResolution,
  12. BACKUP_FORMAT_VERSION
  13. } from '../../viewmodel/PlaylistBackup';
  14. import { AppUtil } from '@pura/harmony-utils';
  15. /**
  16. * 歌单数据库操作类
  17. */
  18. export default class PlaylistTable {
  19. private context: Context;
  20. private rdbStore: relationalStore.RdbStore | null = null;
  21. private initPromise: Promise<void>;
  22. constructor(context: Context) {
  23. this.context = context;
  24. this.initPromise = this.initRdbStore();
  25. }
  26. /**
  27. * 确保数据库已初始化
  28. */
  29. private async ensureInitialized(): Promise<void> {
  30. await this.initPromise;
  31. }
  32. /**
  33. * 初始化数据库
  34. */
  35. private async initRdbStore(): Promise<void> {
  36. try {
  37. const config: relationalStore.StoreConfig = {
  38. name: 'PlaylistStore.db',
  39. securityLevel: relationalStore.SecurityLevel.S1 // 使用标准安全级别
  40. };
  41. this.rdbStore = await relationalStore.getRdbStore(this.context, config);
  42. // 创建表
  43. await this.createTables();
  44. Logger.info('heanup PlaylistTable', '数据库初始化成功');
  45. } catch (error) {
  46. Logger.error('heanup PlaylistTable', `初始化数据库失败: ${error.message}`);
  47. }
  48. }
  49. /**
  50. * 创建表
  51. */
  52. private async createTables(): Promise<void> {
  53. if (!this.rdbStore) {
  54. return;
  55. }
  56. try {
  57. // 创建歌单表
  58. await this.rdbStore.executeSql(RdbUtils.PLAYLIST_TABLE.sqlCreate);
  59. // 创建歌单歌曲关联表
  60. await this.rdbStore.executeSql(RdbUtils.PLAYLIST_SONG_TABLE.sqlCreate);
  61. Logger.info('heanup PlaylistTable', '数据库表创建成功');
  62. } catch (error) {
  63. Logger.error('heanup PlaylistTable', `创建表失败: ${error.message}`);
  64. }
  65. }
  66. /**
  67. * 生成唯一ID
  68. */
  69. private generateId(): string {
  70. return Date.now().toString() + Math.random().toString(36).substr(2, 9);
  71. }
  72. /**
  73. * 创建歌单
  74. */
  75. async createPlaylist(name: string, description?: string, coverPath?: string): Promise<boolean> {
  76. await this.ensureInitialized();
  77. if (!this.rdbStore) {
  78. Logger.error('heanup PlaylistTable', '数据库未初始化');
  79. return false;
  80. }
  81. try {
  82. const id = this.generateId();
  83. const now = new Date().toISOString();
  84. const sql = 'INSERT INTO playlistTable (id, name, coverPath, description, createTime, updateTime, songCount, sortOrder) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
  85. const params = [id, name, coverPath || null, description || null, now, now, 0, 0];
  86. await this.rdbStore.executeSql(sql, params);
  87. Logger.info('heanup PlaylistTable', `歌单创建成功: ${name}`);
  88. this.triggerAutoBackup();
  89. return true;
  90. } catch (error) {
  91. Logger.error('heanup PlaylistTable', `创建歌单失败: ${error.message}`);
  92. return false;
  93. }
  94. }
  95. /**
  96. * 删除歌单及其所有歌曲
  97. */
  98. async deletePlaylist(playlistId: string): Promise<boolean> {
  99. if (!this.rdbStore) {
  100. return false;
  101. }
  102. try {
  103. // 先删除歌单歌曲关联
  104. const deleteSongsSql = 'DELETE FROM playlistSongTable WHERE playlistId = ?';
  105. await this.rdbStore.executeSql(deleteSongsSql, [playlistId]);
  106. // 再删除歌单
  107. const deletePlaylistSql = 'DELETE FROM playlistTable WHERE id = ?';
  108. await this.rdbStore.executeSql(deletePlaylistSql, [playlistId]);
  109. Logger.info('heanup PlaylistTable', `歌单删除成功: ${playlistId}`);
  110. this.triggerAutoBackup();
  111. return true;
  112. } catch (error) {
  113. Logger.error('heanup PlaylistTable', `删除歌单失败: ${error.message}`);
  114. return false;
  115. }
  116. }
  117. /**
  118. * 更新歌单信息
  119. */
  120. async updatePlaylist(playlistId: string, name?: string, description?: string, coverPath?: string): Promise<boolean> {
  121. if (!this.rdbStore) {
  122. return false;
  123. }
  124. try {
  125. const updateTime = new Date().toISOString();
  126. const updates: string[] = [];
  127. const params: (string | number | null)[] = [];
  128. if (name !== undefined) {
  129. updates.push('name = ?');
  130. params.push(name);
  131. }
  132. if (description !== undefined) {
  133. updates.push('description = ?');
  134. params.push(description);
  135. }
  136. if (coverPath !== undefined) {
  137. updates.push('coverPath = ?');
  138. params.push(coverPath);
  139. }
  140. updates.push('updateTime = ?');
  141. params.push(updateTime);
  142. params.push(playlistId);
  143. const sql = `UPDATE playlistTable SET ${updates.join(', ')} WHERE id = ?`;
  144. await this.rdbStore.executeSql(sql, params);
  145. Logger.info('heanup PlaylistTable', `歌单更新成功: ${playlistId}`);
  146. this.triggerAutoBackup();
  147. return true;
  148. } catch (error) {
  149. Logger.error('heanup PlaylistTable', `更新歌单失败: ${error.message}`);
  150. return false;
  151. }
  152. }
  153. /**
  154. * 查询所有歌单
  155. */
  156. async queryAllPlaylists(): Promise<Playlist[]> {
  157. await this.ensureInitialized();
  158. if (!this.rdbStore) {
  159. Logger.error('heanup PlaylistTable', '数据库未初始化');
  160. return [];
  161. }
  162. try {
  163. const sql = 'SELECT * FROM playlistTable ORDER BY sortOrder ASC, createTime DESC';
  164. Logger.info('heanup PlaylistTable', '开始查询所有歌单');
  165. const resultSet = await this.rdbStore.querySql(sql);
  166. const playlists: Playlist[] = [];
  167. if (resultSet.goToFirstRow()) {
  168. do {
  169. const playlistId = resultSet.getString(resultSet.getColumnIndex('id'));
  170. const playlistName = resultSet.getString(resultSet.getColumnIndex('name'));
  171. const songCount = resultSet.getLong(resultSet.getColumnIndex('songCount'));
  172. Logger.info('heanup PlaylistTable', `queryAllPlaylists: 从数据库读取 - ID: ${playlistId}, 名称: ${playlistName}, 歌曲数: ${songCount}`);
  173. const playlist = new Playlist(
  174. playlistId,
  175. playlistName,
  176. resultSet.getString(resultSet.getColumnIndex('createTime')),
  177. resultSet.getString(resultSet.getColumnIndex('updateTime')),
  178. songCount,
  179. resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
  180. resultSet.getString(resultSet.getColumnIndex('coverPath')),
  181. resultSet.getString(resultSet.getColumnIndex('description'))
  182. );
  183. playlists.push(playlist);
  184. } while (resultSet.goToNextRow());
  185. }
  186. resultSet.close();
  187. Logger.info('heanup PlaylistTable', `查询到 ${playlists.length} 个歌单`);
  188. return playlists;
  189. } catch (error) {
  190. Logger.error('heanup PlaylistTable', `查询歌单失败: ${error.message}`);
  191. return [];
  192. }
  193. }
  194. /**
  195. * 根据名称查询最近更新的歌单
  196. */
  197. async queryPlaylistByName(name: string): Promise<Playlist | null> {
  198. await this.ensureInitialized();
  199. if (!this.rdbStore) {
  200. Logger.error('heanup PlaylistTable', '数据库未初始化');
  201. return null;
  202. }
  203. try {
  204. const sql = 'SELECT * FROM playlistTable WHERE name = ? ORDER BY updateTime DESC LIMIT 1';
  205. const resultSet = await this.rdbStore.querySql(sql, [name]);
  206. if (resultSet.goToFirstRow()) {
  207. const playlist = new Playlist(
  208. resultSet.getString(resultSet.getColumnIndex('id')),
  209. resultSet.getString(resultSet.getColumnIndex('name')),
  210. resultSet.getString(resultSet.getColumnIndex('createTime')),
  211. resultSet.getString(resultSet.getColumnIndex('updateTime')),
  212. resultSet.getLong(resultSet.getColumnIndex('songCount')),
  213. resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
  214. resultSet.getString(resultSet.getColumnIndex('coverPath')),
  215. resultSet.getString(resultSet.getColumnIndex('description'))
  216. );
  217. resultSet.close();
  218. return playlist;
  219. }
  220. resultSet.close();
  221. return null;
  222. } catch (error) {
  223. Logger.error('heanup PlaylistTable', `queryPlaylistByName失败: ${error.message}`);
  224. return null;
  225. }
  226. }
  227. /**
  228. * 根据ID查询歌单
  229. */
  230. async queryPlaylistById(playlistId: string): Promise<Playlist | null> {
  231. if (!this.rdbStore) {
  232. return null;
  233. }
  234. try {
  235. const sql = 'SELECT * FROM playlistTable WHERE id = ?';
  236. const resultSet = await this.rdbStore.querySql(sql, [playlistId]);
  237. if (resultSet.goToFirstRow()) {
  238. const playlist = new Playlist(
  239. resultSet.getString(resultSet.getColumnIndex('id')),
  240. resultSet.getString(resultSet.getColumnIndex('name')),
  241. resultSet.getString(resultSet.getColumnIndex('createTime')),
  242. resultSet.getString(resultSet.getColumnIndex('updateTime')),
  243. resultSet.getLong(resultSet.getColumnIndex('songCount')),
  244. resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
  245. resultSet.getString(resultSet.getColumnIndex('coverPath')),
  246. resultSet.getString(resultSet.getColumnIndex('description'))
  247. );
  248. resultSet.close();
  249. return playlist;
  250. }
  251. resultSet.close();
  252. return null;
  253. } catch (error) {
  254. Logger.error('heanup PlaylistTable', `查询歌单失败: ${error.message}`);
  255. return null;
  256. }
  257. }
  258. /**
  259. * 添加歌曲到歌单
  260. */
  261. async addSongToPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
  262. await this.ensureInitialized();
  263. if (!this.rdbStore) {
  264. Logger.error('heanup PlaylistTable', '数据库未初始化');
  265. return false;
  266. }
  267. try {
  268. Logger.info('heanup PlaylistTable', `开始添加歌曲到歌单: playlistId=${playlistId}, songFilePath=${songFilePath}`);
  269. // 检查歌曲是否已在歌单中
  270. const isInPlaylist = await this.isSongInPlaylist(playlistId, songFilePath);
  271. if (isInPlaylist) {
  272. Logger.info('heanup PlaylistTable', '歌曲已在歌单中');
  273. return false;
  274. }
  275. const id = this.generateId();
  276. const addTime = new Date().toISOString();
  277. const sql = 'INSERT INTO playlistSongTable (id, playlistId, songFilePath, addTime, sortOrder) VALUES (?, ?, ?, ?, ?)';
  278. const params = [id, playlistId, songFilePath, addTime, 0];
  279. Logger.info('heanup PlaylistTable', `执行SQL插入: ${sql}`);
  280. await this.rdbStore.executeSql(sql, params);
  281. Logger.info('heanup PlaylistTable', '歌曲插入成功');
  282. // 更新歌单歌曲数量
  283. Logger.info('heanup PlaylistTable', '开始更新歌单歌曲数量');
  284. await this.updatePlaylistSongCount(playlistId);
  285. Logger.info('heanup PlaylistTable', `歌曲添加到歌单成功: ${songFilePath}`);
  286. this.triggerAutoBackup();
  287. return true;
  288. } catch (error) {
  289. Logger.error('heanup PlaylistTable', `添加歌曲到歌单失败: ${error.message}`);
  290. return false;
  291. }
  292. }
  293. /**
  294. * 批量添加歌曲到歌单
  295. */
  296. async addSongsToPlaylist(playlistId: string, songFilePaths: string[]): Promise<boolean> {
  297. await this.ensureInitialized();
  298. if (!this.rdbStore) {
  299. Logger.error('heanup PlaylistTable', '数据库未初始化');
  300. return false;
  301. }
  302. if (!songFilePaths || songFilePaths.length === 0) {
  303. Logger.error('heanup PlaylistTable', '歌曲路径列表为空');
  304. return false;
  305. }
  306. try {
  307. Logger.info('heanup PlaylistTable', `开始批量添加歌曲到歌单: playlistId=${playlistId}, 歌曲数量=${songFilePaths.length}`);
  308. let successCount = 0;
  309. for (const songFilePath of songFilePaths) {
  310. try {
  311. // 检查歌曲是否已在歌单中
  312. const isInPlaylist = await this.isSongInPlaylist(playlistId, songFilePath);
  313. if (isInPlaylist) {
  314. Logger.info('heanup PlaylistTable', `歌曲已在歌单中,跳过: ${songFilePath}`);
  315. continue;
  316. }
  317. const id = this.generateId();
  318. const addTime = new Date().toISOString();
  319. const sql = 'INSERT INTO playlistSongTable (id, playlistId, songFilePath, addTime, sortOrder) VALUES (?, ?, ?, ?, ?)';
  320. const params = [id, playlistId, songFilePath, addTime, 0];
  321. await this.rdbStore.executeSql(sql, params);
  322. successCount++;
  323. Logger.info('heanup PlaylistTable', `成功添加歌曲: ${songFilePath}`);
  324. } catch (error) {
  325. Logger.error('heanup PlaylistTable', `添加歌曲失败: ${songFilePath}, 错误: ${error.message}`);
  326. }
  327. }
  328. // 更新歌单歌曲数量
  329. Logger.info('heanup PlaylistTable', '开始更新歌单歌曲数量');
  330. await this.updatePlaylistSongCount(playlistId);
  331. Logger.info('heanup PlaylistTable', `批量添加歌曲完成,成功添加 ${successCount} 首,共 ${songFilePaths.length} 首`);
  332. return successCount > 0;
  333. } catch (error) {
  334. Logger.error('heanup PlaylistTable', `批量添加歌曲到歌单失败: ${error.message}`);
  335. return false;
  336. }
  337. }
  338. /**
  339. * 从歌单中移除歌曲
  340. */
  341. async removeSongFromPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
  342. if (!this.rdbStore) {
  343. Logger.error('heanup PlaylistTable', 'removeSongFromPlaylist: 数据库未初始化');
  344. return false;
  345. }
  346. try {
  347. Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 开始删除歌曲 ${songFilePath} 从歌单 ${playlistId}`);
  348. const sql = 'DELETE FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
  349. await this.rdbStore.executeSql(sql, [playlistId, songFilePath]);
  350. Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 数据库删除操作完成`);
  351. // 更新歌单歌曲数量
  352. Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 开始更新歌单数量`);
  353. await this.updatePlaylistSongCount(playlistId);
  354. Logger.info('heanup PlaylistTable', `removeSongFromPlaylist: 歌曲从歌单移除成功: ${songFilePath}`);
  355. this.triggerAutoBackup();
  356. return true;
  357. } catch (error) {
  358. Logger.error('heanup PlaylistTable', `removeSongFromPlaylist: 从歌单移除歌曲失败: ${error.message}`);
  359. return false;
  360. }
  361. }
  362. /**
  363. * 从所有歌单中移除指定歌曲(当歌曲文件被删除时调用)
  364. */
  365. async removeSongFromAllPlaylists(songFilePath: string): Promise<string[]> {
  366. if (!this.rdbStore) {
  367. Logger.error('heanup PlaylistTable', 'removeSongFromAllPlaylists: 数据库未初始化');
  368. return [];
  369. }
  370. try {
  371. Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 开始处理歌曲 ${songFilePath}`);
  372. // 1. 先查询这首歌在哪些歌单中
  373. const sql = 'SELECT DISTINCT playlistId FROM playlistSongTable WHERE songFilePath = ?';
  374. const resultSet = await this.rdbStore.querySql(sql, [songFilePath]);
  375. const affectedPlaylistIds: string[] = [];
  376. if (resultSet.goToFirstRow()) {
  377. do {
  378. const playlistId = resultSet.getString(0);
  379. affectedPlaylistIds.push(playlistId);
  380. Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 找到歌单 ${playlistId}`);
  381. } while (resultSet.goToNextRow());
  382. }
  383. resultSet.close();
  384. if (affectedPlaylistIds.length === 0) {
  385. Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 歌曲不在任何歌单中: ${songFilePath}`);
  386. return [];
  387. }
  388. Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 歌曲在 ${affectedPlaylistIds.length} 个歌单中`);
  389. // 2. 从所有歌单中删除这首歌
  390. const deleteSql = 'DELETE FROM playlistSongTable WHERE songFilePath = ?';
  391. await this.rdbStore.executeSql(deleteSql, [songFilePath]);
  392. Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 已从数据库删除歌曲记录`);
  393. // 3. 更新所有受影响歌单的歌曲数量
  394. for (const playlistId of affectedPlaylistIds) {
  395. Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 开始更新歌单 ${playlistId} 的数量`);
  396. await this.updatePlaylistSongCount(playlistId);
  397. }
  398. Logger.info('heanup PlaylistTable', `removeSongFromAllPlaylists: 歌曲已从 ${affectedPlaylistIds.length} 个歌单中移除: ${songFilePath}`);
  399. return affectedPlaylistIds;
  400. } catch (error) {
  401. Logger.error('heanup PlaylistTable', `removeSongFromAllPlaylists: 从所有歌单移除歌曲失败: ${error.message}`);
  402. return [];
  403. }
  404. }
  405. /**
  406. * 查询歌单中的歌曲
  407. */
  408. async queryPlaylistSongs(playlistId: string): Promise<PlaylistSong[]> {
  409. await this.ensureInitialized();
  410. if (!this.rdbStore) {
  411. Logger.error('heanup PlaylistTable', '数据库未初始化');
  412. return [];
  413. }
  414. try {
  415. const sql = 'SELECT * FROM playlistSongTable WHERE playlistId = ? ORDER BY sortOrder ASC, addTime ASC';
  416. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 开始查询歌单歌曲, playlistId=${playlistId}`);
  417. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: SQL=${sql}`);
  418. const resultSet = await this.rdbStore.querySql(sql, [playlistId]);
  419. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 查询执行完成, rowCount=${resultSet.rowCount}`);
  420. const songs: PlaylistSong[] = [];
  421. if (resultSet.goToFirstRow()) {
  422. Logger.info('heanup PlaylistTable', 'queryPlaylistSongs: 移动到第一行成功');
  423. do {
  424. const song = new PlaylistSong(
  425. resultSet.getString(resultSet.getColumnIndex('id')),
  426. resultSet.getString(resultSet.getColumnIndex('playlistId')),
  427. resultSet.getString(resultSet.getColumnIndex('songFilePath')),
  428. resultSet.getString(resultSet.getColumnIndex('addTime')),
  429. resultSet.getLong(resultSet.getColumnIndex('sortOrder'))
  430. );
  431. songs.push(song);
  432. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 添加歌曲, songFilePath=${song.songFilePath}`);
  433. } while (resultSet.goToNextRow());
  434. } else {
  435. Logger.info('heanup PlaylistTable', 'queryPlaylistSongs: 没有数据,无法移动到第一行');
  436. }
  437. resultSet.close();
  438. Logger.info('heanup PlaylistTable', `queryPlaylistSongs: 查询到 ${songs.length} 首歌曲`);
  439. return songs;
  440. } catch (error) {
  441. Logger.error('heanup PlaylistTable', `queryPlaylistSongs: 查询歌单歌曲失败: ${error.message}`);
  442. Logger.error('heanup PlaylistTable', `queryPlaylistSongs: 错误堆栈: ${error.stack || '无堆栈信息'}`);
  443. return [];
  444. }
  445. }
  446. /**
  447. * 检查歌曲是否在歌单中
  448. */
  449. async isSongInPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
  450. if (!this.rdbStore) {
  451. return false;
  452. }
  453. try {
  454. const sql = 'SELECT COUNT(*) as count FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
  455. const resultSet = await this.rdbStore.querySql(sql, [playlistId, songFilePath]);
  456. let isInPlaylist = false;
  457. if (resultSet.goToFirstRow()) {
  458. const count = resultSet.getLong(resultSet.getColumnIndex('count'));
  459. isInPlaylist = count > 0;
  460. }
  461. resultSet.close();
  462. return isInPlaylist;
  463. } catch (error) {
  464. Logger.error('heanup PlaylistTable', `检查歌曲是否在歌单中失败: ${error.message}`);
  465. return false;
  466. }
  467. }
  468. /**
  469. * 更新歌单歌曲数量
  470. */
  471. private async updatePlaylistSongCount(playlistId: string): Promise<void> {
  472. if (!this.rdbStore) {
  473. Logger.error('heanup PlaylistTable', 'updatePlaylistSongCount: 数据库未初始化');
  474. return;
  475. }
  476. try {
  477. Logger.info('heanup PlaylistTable', `updatePlaylistSongCount: 开始更新歌单 ${playlistId} 的歌曲数量`);
  478. const countSql = 'SELECT COUNT(*) as count FROM playlistSongTable WHERE playlistId = ?';
  479. const resultSet = await this.rdbStore.querySql(countSql, [playlistId]);
  480. let count = 0;
  481. if (resultSet.goToFirstRow()) {
  482. count = resultSet.getLong(resultSet.getColumnIndex('count'));
  483. }
  484. resultSet.close();
  485. Logger.info('heanup PlaylistTable', `updatePlaylistSongCount: 统计到 ${count} 首歌曲`);
  486. const updateSql = 'UPDATE playlistTable SET songCount = ?, updateTime = ? WHERE id = ?';
  487. const updateTime = new Date().toISOString();
  488. await this.rdbStore.executeSql(updateSql, [count, updateTime, playlistId]);
  489. Logger.info('heanup PlaylistTable', `updatePlaylistSongCount: 数据库更新成功,songCount = ${count}`);
  490. } catch (error) {
  491. Logger.error('heanup PlaylistTable', `updatePlaylistSongCount: 更新歌单歌曲数量失败: ${error.message}`);
  492. }
  493. }
  494. /**
  495. * 强制同步歌单歌曲数量(公开方法,用于修复数据不一致)
  496. */
  497. async syncPlaylistSongCount(playlistId: string): Promise<void> {
  498. await this.ensureInitialized();
  499. await this.updatePlaylistSongCount(playlistId);
  500. Logger.info('heanup PlaylistTable', `已强制同步歌单 ${playlistId} 的歌曲数量`);
  501. }
  502. /**
  503. * 清理歌单中无效的歌曲记录(歌曲文件已不存在)
  504. */
  505. async removeInvalidSongFromPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
  506. if (!this.rdbStore) {
  507. Logger.error('heanup PlaylistTable', 'removeInvalidSongFromPlaylist: 数据库未初始化');
  508. return false;
  509. }
  510. try {
  511. Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 开始清理无效歌曲 ${songFilePath} 从歌单 ${playlistId}`);
  512. const sql = 'DELETE FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
  513. await this.rdbStore.executeSql(sql, [playlistId, songFilePath]);
  514. Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 数据库删除操作完成`);
  515. // 更新歌单歌曲数量
  516. Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 开始更新歌单数量`);
  517. await this.updatePlaylistSongCount(playlistId);
  518. Logger.info('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 无效歌曲从歌单清理成功: ${songFilePath}`);
  519. return true;
  520. } catch (error) {
  521. Logger.error('heanup PlaylistTable', `removeInvalidSongFromPlaylist: 清理无效歌曲失败: ${error.message}`);
  522. return false;
  523. }
  524. }
  525. /**
  526. * 更新歌单排序
  527. */
  528. async updatePlaylistSortOrder(playlistId: string, sortOrder: number): Promise<boolean> {
  529. if (!this.rdbStore) {
  530. return false;
  531. }
  532. try {
  533. const updateTime = new Date().toISOString();
  534. const sql = 'UPDATE playlistTable SET sortOrder = ?, updateTime = ? WHERE id = ?';
  535. await this.rdbStore.executeSql(sql, [sortOrder, updateTime, playlistId]);
  536. Logger.info('heanup PlaylistTable', `歌单排序更新成功: ${playlistId}`);
  537. return true;
  538. } catch (error) {
  539. Logger.error('heanup PlaylistTable', `更新歌单排序失败: ${error.message}`);
  540. return false;
  541. }
  542. }
  543. /**
  544. * 更新歌单歌曲排序
  545. */
  546. async updatePlaylistSongSortOrder(playlistId: string, songFilePath: string, sortOrder: number): Promise<boolean> {
  547. if (!this.rdbStore) {
  548. Logger.info('heanup PlaylistTable', `rdbStore未初始化: ${songFilePath}`);
  549. return false;
  550. }
  551. try {
  552. const sql = 'UPDATE playlistSongTable SET sortOrder = ? WHERE playlistId = ? AND songFilePath = ?';
  553. await this.rdbStore.executeSql(sql, [sortOrder, playlistId, songFilePath]);
  554. Logger.info('heanup PlaylistTable', `歌单歌曲排序更新成功: ${songFilePath}`);
  555. return true;
  556. } catch (error) {
  557. Logger.error('heanup PlaylistTable', `更新歌单歌曲排序失败: ${error.message}`);
  558. return false;
  559. }
  560. }
  561. /**
  562. * 导出所有歌单及其歌曲为备份数据
  563. */
  564. async exportAllPlaylists(): Promise<PlaylistBackupData> {
  565. await this.ensureInitialized();
  566. const backupData: PlaylistBackupData = {
  567. version: BACKUP_FORMAT_VERSION,
  568. exportTime: new Date().toISOString(),
  569. appVersion: AppUtil.getVersionName(),
  570. playlists: []
  571. };
  572. if (!this.rdbStore) {
  573. Logger.error('heanup PlaylistTable', 'exportAllPlaylists: 数据库未初始化');
  574. return backupData;
  575. }
  576. try {
  577. const playlists = await this.queryAllPlaylists();
  578. Logger.info('heanup PlaylistTable', `exportAllPlaylists: 开始导出 ${playlists.length} 个歌单`);
  579. for (let i = 0; i < playlists.length; i++) {
  580. const playlist = playlists[i];
  581. const songs = await this.queryPlaylistSongs(playlist.id);
  582. const songBackups: PlaylistSongBackupItem[] = [];
  583. for (let j = 0; j < songs.length; j++) {
  584. const song = songs[j];
  585. songBackups.push({
  586. songFilePath: song.songFilePath,
  587. addTime: song.addTime,
  588. sortOrder: song.sortOrder
  589. });
  590. }
  591. const playlistBackup: PlaylistBackupItem = {
  592. id: playlist.id,
  593. name: playlist.name,
  594. coverPath: playlist.coverPath || '',
  595. description: playlist.description || '',
  596. createTime: playlist.createTime,
  597. updateTime: playlist.updateTime,
  598. songCount: playlist.songCount,
  599. sortOrder: playlist.sortOrder,
  600. songs: songBackups
  601. };
  602. backupData.playlists.push(playlistBackup);
  603. }
  604. Logger.info('heanup PlaylistTable', `exportAllPlaylists: 导出完成, ${backupData.playlists.length} 个歌单`);
  605. return backupData;
  606. } catch (error) {
  607. Logger.error('heanup PlaylistTable', `exportAllPlaylists: 导出失败: ${(error as Error).message}`);
  608. return backupData;
  609. }
  610. }
  611. /**
  612. * 从备份数据导入歌单
  613. * @param backupData 备份数据
  614. * @param conflictStrategy 冲突解决策略(null 表示需要逐一询问,由调用方处理)
  615. * @param conflictsToSkip 需要跳过的歌单名称集合
  616. * @param conflictsToOverwrite 需要覆盖的歌单名称集合
  617. * @param conflictsToRename 需要重命名的歌单名称集合
  618. */
  619. async importPlaylists(
  620. backupData: PlaylistBackupData,
  621. conflictsToSkip: Set<string>,
  622. conflictsToOverwrite: Set<string>,
  623. conflictsToRename: Set<string>
  624. ): Promise<ImportResult> {
  625. await this.ensureInitialized();
  626. const result: ImportResult = {
  627. totalPlaylists: backupData.playlists.length,
  628. importedPlaylists: 0,
  629. skippedPlaylists: 0,
  630. overwrittenPlaylists: 0,
  631. renamedPlaylists: 0,
  632. totalSongs: 0,
  633. importedSongs: 0,
  634. importedAccounts: 0,
  635. skippedAccounts: 0,
  636. settingsImported: false
  637. };
  638. if (!this.rdbStore) {
  639. Logger.error('heanup PlaylistTable', 'importPlaylists: 数据库未初始化');
  640. return result;
  641. }
  642. try {
  643. for (let i = 0; i < backupData.playlists.length; i++) {
  644. const playlistItem = backupData.playlists[i];
  645. result.totalSongs += playlistItem.songs.length;
  646. const existingPlaylist = await this.queryPlaylistByName(playlistItem.name);
  647. if (existingPlaylist) {
  648. // 存在同名歌单,按策略处理
  649. if (conflictsToSkip.has(playlistItem.name)) {
  650. result.skippedPlaylists++;
  651. Logger.info('heanup PlaylistTable', `importPlaylists: 跳过歌单: ${playlistItem.name}`);
  652. continue;
  653. } else if (conflictsToOverwrite.has(playlistItem.name)) {
  654. // 覆盖:先删除旧歌单
  655. await this.deletePlaylist(existingPlaylist.id);
  656. result.overwrittenPlaylists++;
  657. Logger.info('heanup PlaylistTable', `importPlaylists: 覆盖歌单: ${playlistItem.name}`);
  658. } else if (conflictsToRename.has(playlistItem.name)) {
  659. // 重命名
  660. const newName = await this.generateUniqueName(playlistItem.name);
  661. playlistItem.name = newName;
  662. result.renamedPlaylists++;
  663. Logger.info('heanup PlaylistTable', `importPlaylists: 重命名歌单为: ${newName}`);
  664. } else {
  665. // 默认跳过
  666. result.skippedPlaylists++;
  667. continue;
  668. }
  669. }
  670. // 创建歌单
  671. const created = await this.createPlaylist(
  672. playlistItem.name,
  673. playlistItem.description,
  674. playlistItem.coverPath
  675. );
  676. if (!created) {
  677. Logger.error('heanup PlaylistTable', `importPlaylists: 创建歌单失败: ${playlistItem.name}`);
  678. continue;
  679. }
  680. // 查询刚创建的歌单
  681. const newPlaylist = await this.queryPlaylistByName(playlistItem.name);
  682. if (!newPlaylist) {
  683. Logger.error('heanup PlaylistTable', `importPlaylists: 找不到新建歌单: ${playlistItem.name}`);
  684. continue;
  685. }
  686. result.importedPlaylists++;
  687. // 导入歌曲关联
  688. const songPaths: string[] = [];
  689. for (let j = 0; j < playlistItem.songs.length; j++) {
  690. songPaths.push(playlistItem.songs[j].songFilePath);
  691. }
  692. if (songPaths.length > 0) {
  693. const addResult = await this.addSongsToPlaylist(newPlaylist.id, songPaths);
  694. if (addResult) {
  695. result.importedSongs += songPaths.length;
  696. }
  697. }
  698. Logger.info('heanup PlaylistTable', `importPlaylists: 导入歌单成功: ${playlistItem.name}, ${songPaths.length} 首歌曲`);
  699. }
  700. Logger.info('heanup PlaylistTable', `importPlaylists: 导入完成, 共 ${result.importedPlaylists} 个歌单, ${result.importedSongs} 首歌曲`);
  701. return result;
  702. } catch (error) {
  703. Logger.error('heanup PlaylistTable', `importPlaylists: 导入失败: ${(error as Error).message}`);
  704. return result;
  705. }
  706. }
  707. /**
  708. * 生成不重复的歌单名称
  709. */
  710. private async generateUniqueName(baseName: string): Promise<string> {
  711. let suffix = 2;
  712. let candidateName = `${baseName} (${suffix})`;
  713. while (true) {
  714. const existing = await this.queryPlaylistByName(candidateName);
  715. if (!existing) {
  716. return candidateName;
  717. }
  718. suffix++;
  719. candidateName = `${baseName} (${suffix})`;
  720. }
  721. }
  722. /**
  723. * 检测备份数据中与现有歌单的冲突
  724. */
  725. async detectConflicts(backupData: PlaylistBackupData): Promise<string[]> {
  726. await this.ensureInitialized();
  727. const conflicts: string[] = [];
  728. for (let i = 0; i < backupData.playlists.length; i++) {
  729. const playlistItem = backupData.playlists[i];
  730. const existing = await this.queryPlaylistByName(playlistItem.name);
  731. if (existing) {
  732. conflicts.push(playlistItem.name);
  733. }
  734. }
  735. return conflicts;
  736. }
  737. /**
  738. * 自动备份回调(由 PlaylistBackupManager 注册,避免循环依赖)
  739. */
  740. private static autoBackupCallback: (() => void) | null = null;
  741. static registerAutoBackupCallback(callback: () => void): void {
  742. PlaylistTable.autoBackupCallback = callback;
  743. }
  744. /**
  745. * 触发自动备份
  746. */
  747. private triggerAutoBackup(): void {
  748. if (PlaylistTable.autoBackupCallback) {
  749. PlaylistTable.autoBackupCallback();
  750. }
  751. }
  752. }