xref: /MusicFree/src/core/localMusicSheet.ts (revision 0224b88128448f4103c1d3e38e4da46f1b5945ee)
10e4173cdS猫头猫import {internalSerializeKey, StorageKeys} from '@/constants/commonConst';
2afb5c234S猫头猫import mp3Util, {IBasicMeta} from '@/native/mp3Util';
388772aabS猫头猫import {
488772aabS猫头猫    getInternalData,
588772aabS猫头猫    InternalDataType,
688772aabS猫头猫    isSameMediaItem,
788772aabS猫头猫} from '@/utils/mediaItem';
8afb5c234S猫头猫import StateMapper from '@/utils/stateMapper';
9afb5c234S猫头猫import {getStorage, setStorage} from '@/utils/storage';
100e4173cdS猫头猫import {useEffect, useState} from 'react';
11afb5c234S猫头猫import {FileSystem} from 'react-native-file-access';
12afb5c234S猫头猫
13afb5c234S猫头猫let localSheet: IMusic.IMusicItem[] = [];
14afb5c234S猫头猫const localSheetStateMapper = new StateMapper(() => localSheet);
15afb5c234S猫头猫
16afb5c234S猫头猫export async function setup() {
1788772aabS猫头猫    const sheet = await getStorage(StorageKeys.LocalMusicSheet);
1888772aabS猫头猫    if (sheet) {
1988772aabS猫头猫        let validSheet = [];
2088772aabS猫头猫        for (let musicItem of sheet) {
2188772aabS猫头猫            const localPath = getInternalData<string>(
2288772aabS猫头猫                musicItem,
2388772aabS猫头猫                InternalDataType.LOCALPATH,
2488772aabS猫头猫            );
2588772aabS猫头猫            if (localPath && (await FileSystem.exists(localPath))) {
2688772aabS猫头猫                validSheet.push(musicItem);
2788772aabS猫头猫            }
2888772aabS猫头猫        }
2988772aabS猫头猫        if (validSheet.length !== sheet.length) {
3088772aabS猫头猫            await setStorage(StorageKeys.LocalMusicSheet, validSheet);
3188772aabS猫头猫        }
3288772aabS猫头猫        localSheet = validSheet;
33afb5c234S猫头猫    } else {
34afb5c234S猫头猫        await setStorage(StorageKeys.LocalMusicSheet, []);
35afb5c234S猫头猫    }
36afb5c234S猫头猫    localSheetStateMapper.notify();
37afb5c234S猫头猫}
38afb5c234S猫头猫
39afb5c234S猫头猫export async function addMusic(
40afb5c234S猫头猫    musicItem: IMusic.IMusicItem | IMusic.IMusicItem[],
41afb5c234S猫头猫) {
42afb5c234S猫头猫    if (!Array.isArray(musicItem)) {
43afb5c234S猫头猫        musicItem = [musicItem];
44afb5c234S猫头猫    }
45afb5c234S猫头猫    let newSheet = [...localSheet];
46afb5c234S猫头猫    musicItem.forEach(mi => {
47afb5c234S猫头猫        if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) {
48afb5c234S猫头猫            newSheet.push(mi);
49afb5c234S猫头猫        }
50afb5c234S猫头猫    });
51afb5c234S猫头猫    await setStorage(StorageKeys.LocalMusicSheet, newSheet);
52afb5c234S猫头猫    localSheet = newSheet;
53afb5c234S猫头猫    localSheetStateMapper.notify();
54afb5c234S猫头猫}
55afb5c234S猫头猫
56afb5c234S猫头猫export async function removeMusic(
57afb5c234S猫头猫    musicItem: IMusic.IMusicItem,
58afb5c234S猫头猫    deleteOriginalFile = false,
59afb5c234S猫头猫) {
60afb5c234S猫头猫    const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem));
61afb5c234S猫头猫    let newSheet = [...localSheet];
62afb5c234S猫头猫    if (idx !== -1) {
63afb5c234S猫头猫        newSheet.splice(idx, 1);
640e4173cdS猫头猫        if (deleteOriginalFile && musicItem[internalSerializeKey]?.localPath) {
650e4173cdS猫头猫            await FileSystem.unlink(musicItem[internalSerializeKey].localPath);
66afb5c234S猫头猫        }
67afb5c234S猫头猫    }
68afb5c234S猫头猫    localSheet = newSheet;
69afb5c234S猫头猫    localSheetStateMapper.notify();
70afb5c234S猫头猫}
71afb5c234S猫头猫
72afb5c234S猫头猫function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null {
73afb5c234S猫头猫    const data = fn.slice(0, fn.lastIndexOf('.')).split('@');
74afb5c234S猫头猫    const [platform, id, title, artist] = data;
75afb5c234S猫头猫    if (!platform || !id) {
76afb5c234S猫头猫        return null;
77afb5c234S猫头猫    }
78afb5c234S猫头猫    return {
79afb5c234S猫头猫        id,
80afb5c234S猫头猫        platform,
81afb5c234S猫头猫        title,
82afb5c234S猫头猫        artist,
83afb5c234S猫头猫    };
84afb5c234S猫头猫}
85afb5c234S猫头猫
860e4173cdS猫头猫/** 从文件夹导入 */
870e4173cdS猫头猫async function importFolder(folderPath: string) {
88afb5c234S猫头猫    const dirFiles = await FileSystem.statDir(folderPath);
89afb5c234S猫头猫    const musicFiles = dirFiles.filter(
90afb5c234S猫头猫        _ => _.type === 'file' && _.filename.endsWith('.mp3'),
91afb5c234S猫头猫    );
92afb5c234S猫头猫
93afb5c234S猫头猫    const musicItems: IMusic.IMusicItem[] = await Promise.all(
94afb5c234S猫头猫        musicFiles.map(async mf => {
95afb5c234S猫头猫            let {platform, id, title, artist} =
96afb5c234S猫头猫                parseFilename(mf.filename) ?? {};
97afb5c234S猫头猫
98afb5c234S猫头猫            let meta: IBasicMeta | null;
99afb5c234S猫头猫            try {
100afb5c234S猫头猫                meta = await mp3Util.getBasicMeta(mf.path);
101afb5c234S猫头猫            } catch {
102afb5c234S猫头猫                meta = null;
103afb5c234S猫头猫            }
104afb5c234S猫头猫            if (!platform || !id) {
105afb5c234S猫头猫                platform = '本地';
106afb5c234S猫头猫                id = await FileSystem.hash(mf.path, 'MD5');
107afb5c234S猫头猫            }
108afb5c234S猫头猫            return {
109afb5c234S猫头猫                id,
110afb5c234S猫头猫                platform,
111afb5c234S猫头猫                title: title ?? meta?.title ?? '未知名称',
112afb5c234S猫头猫                artist: artist ?? meta?.artist ?? '未知歌手',
113afb5c234S猫头猫                duration: parseInt(meta?.duration ?? '0') / 1000,
114afb5c234S猫头猫                album: meta?.album ?? '',
115afb5c234S猫头猫                artwork: '',
1160e4173cdS猫头猫                [internalSerializeKey]: {
1170e4173cdS猫头猫                    localPath: mf.path,
118afb5c234S猫头猫                },
119afb5c234S猫头猫            };
120afb5c234S猫头猫        }),
121afb5c234S猫头猫    );
122afb5c234S猫头猫    addMusic(musicItems);
123afb5c234S猫头猫}
1240e4173cdS猫头猫
1250e4173cdS猫头猫/** 是否为本地音乐 */
1260e4173cdS猫头猫function isLocalMusic(
1270e4173cdS猫头猫    musicItem: ICommon.IMediaBase | null,
1280e4173cdS猫头猫): IMusic.IMusicItem | undefined {
1290e4173cdS猫头猫    return musicItem
1300e4173cdS猫头猫        ? localSheet.find(_ => isSameMediaItem(_, musicItem))
1310e4173cdS猫头猫        : undefined;
1320e4173cdS猫头猫}
1330e4173cdS猫头猫
1340e4173cdS猫头猫/** 状态-是否为本地音乐 */
1350e4173cdS猫头猫function useIsLocal(musicItem: IMusic.IMusicItem | null) {
1360e4173cdS猫头猫    const localMusicState = localSheetStateMapper.useMappedState();
1370e4173cdS猫头猫    const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem));
1380e4173cdS猫头猫    useEffect(() => {
1390e4173cdS猫头猫        if (!musicItem) {
1400e4173cdS猫头猫            setIsLocal(false);
1410e4173cdS猫头猫        } else {
1420e4173cdS猫头猫            setIsLocal(!!isLocalMusic(musicItem));
1430e4173cdS猫头猫        }
1440e4173cdS猫头猫    }, [localMusicState, musicItem]);
1450e4173cdS猫头猫    return isLocal;
1460e4173cdS猫头猫}
1470e4173cdS猫头猫
148*0224b881S猫头猫function getMusicList() {
149*0224b881S猫头猫    return localSheet;
150*0224b881S猫头猫}
151*0224b881S猫头猫
1520e4173cdS猫头猫const LocalMusicSheet = {
1530e4173cdS猫头猫    setup,
1540e4173cdS猫头猫    addMusic,
1550e4173cdS猫头猫    removeMusic,
1560e4173cdS猫头猫    importFolder,
1570e4173cdS猫头猫    isLocalMusic,
1580e4173cdS猫头猫    useIsLocal,
159*0224b881S猫头猫    getMusicList,
1600e4173cdS猫头猫    useMusicList: localSheetStateMapper.useMappedState,
1610e4173cdS猫头猫};
1620e4173cdS猫头猫
1630e4173cdS猫头猫export default LocalMusicSheet;
164