xref: /MusicFree/src/core/localMusicSheet.ts (revision a8557e3d6ea242cef02b2179cc42c9f209dad97c)
18fc75cb2S猫头猫import {
28fc75cb2S猫头猫    internalSerializeKey,
38fc75cb2S猫头猫    StorageKeys,
48fc75cb2S猫头猫    supportLocalMediaType,
58fc75cb2S猫头猫} from '@/constants/commonConst';
68423b2d4S猫头猫import mp3Util, {IBasicMeta} from '@/native/mp3Util';
788772aabS猫头猫import {
888772aabS猫头猫    getInternalData,
988772aabS猫头猫    InternalDataType,
1088772aabS猫头猫    isSameMediaItem,
1188772aabS猫头猫} from '@/utils/mediaItem';
12afb5c234S猫头猫import StateMapper from '@/utils/stateMapper';
13afb5c234S猫头猫import {getStorage, setStorage} from '@/utils/storage';
14806b2764S猫头猫import {nanoid} from 'nanoid';
150e4173cdS猫头猫import {useEffect, useState} from 'react';
16cd669353S猫头猫import {FileStat, FileSystem} from 'react-native-file-access';
17afb5c234S猫头猫
18afb5c234S猫头猫let localSheet: IMusic.IMusicItem[] = [];
19afb5c234S猫头猫const localSheetStateMapper = new StateMapper(() => localSheet);
20afb5c234S猫头猫
21afb5c234S猫头猫export async function setup() {
2288772aabS猫头猫    const sheet = await getStorage(StorageKeys.LocalMusicSheet);
2388772aabS猫头猫    if (sheet) {
2488772aabS猫头猫        let validSheet = [];
2588772aabS猫头猫        for (let musicItem of sheet) {
2688772aabS猫头猫            const localPath = getInternalData<string>(
2788772aabS猫头猫                musicItem,
2888772aabS猫头猫                InternalDataType.LOCALPATH,
2988772aabS猫头猫            );
3088772aabS猫头猫            if (localPath && (await FileSystem.exists(localPath))) {
3188772aabS猫头猫                validSheet.push(musicItem);
3288772aabS猫头猫            }
3388772aabS猫头猫        }
3488772aabS猫头猫        if (validSheet.length !== sheet.length) {
3588772aabS猫头猫            await setStorage(StorageKeys.LocalMusicSheet, validSheet);
3688772aabS猫头猫        }
3788772aabS猫头猫        localSheet = validSheet;
38afb5c234S猫头猫    } else {
39afb5c234S猫头猫        await setStorage(StorageKeys.LocalMusicSheet, []);
40afb5c234S猫头猫    }
41afb5c234S猫头猫    localSheetStateMapper.notify();
42afb5c234S猫头猫}
43afb5c234S猫头猫
44afb5c234S猫头猫export async function addMusic(
45afb5c234S猫头猫    musicItem: IMusic.IMusicItem | IMusic.IMusicItem[],
46afb5c234S猫头猫) {
47afb5c234S猫头猫    if (!Array.isArray(musicItem)) {
48afb5c234S猫头猫        musicItem = [musicItem];
49afb5c234S猫头猫    }
50afb5c234S猫头猫    let newSheet = [...localSheet];
51afb5c234S猫头猫    musicItem.forEach(mi => {
52afb5c234S猫头猫        if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) {
53afb5c234S猫头猫            newSheet.push(mi);
54afb5c234S猫头猫        }
55afb5c234S猫头猫    });
56afb5c234S猫头猫    await setStorage(StorageKeys.LocalMusicSheet, newSheet);
57afb5c234S猫头猫    localSheet = newSheet;
58afb5c234S猫头猫    localSheetStateMapper.notify();
59afb5c234S猫头猫}
60afb5c234S猫头猫
61dc160d50S猫头猫function addMusicDraft(musicItem: IMusic.IMusicItem | IMusic.IMusicItem[]) {
62dc160d50S猫头猫    if (!Array.isArray(musicItem)) {
63dc160d50S猫头猫        musicItem = [musicItem];
64dc160d50S猫头猫    }
65dc160d50S猫头猫    let newSheet = [...localSheet];
66dc160d50S猫头猫    musicItem.forEach(mi => {
67dc160d50S猫头猫        if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) {
68dc160d50S猫头猫            newSheet.push(mi);
69dc160d50S猫头猫        }
70dc160d50S猫头猫    });
71dc160d50S猫头猫    localSheet = newSheet;
72dc160d50S猫头猫    localSheetStateMapper.notify();
73dc160d50S猫头猫}
74dc160d50S猫头猫
75dc160d50S猫头猫async function saveLocalSheet() {
76dc160d50S猫头猫    await setStorage(StorageKeys.LocalMusicSheet, localSheet);
77dc160d50S猫头猫}
78dc160d50S猫头猫
79afb5c234S猫头猫export async function removeMusic(
80afb5c234S猫头猫    musicItem: IMusic.IMusicItem,
81afb5c234S猫头猫    deleteOriginalFile = false,
82afb5c234S猫头猫) {
83afb5c234S猫头猫    const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem));
84afb5c234S猫头猫    let newSheet = [...localSheet];
85afb5c234S猫头猫    if (idx !== -1) {
86e65882dbS猫头猫        const localMusicItem = localSheet[idx];
87afb5c234S猫头猫        newSheet.splice(idx, 1);
88e65882dbS猫头猫        const localPath =
89e65882dbS猫头猫            musicItem[internalSerializeKey]?.localPath ??
90e65882dbS猫头猫            localMusicItem[internalSerializeKey]?.localPath;
91e65882dbS猫头猫        if (deleteOriginalFile && localPath) {
92e65882dbS猫头猫            await FileSystem.unlink(localPath);
93afb5c234S猫头猫        }
94afb5c234S猫头猫    }
95afb5c234S猫头猫    localSheet = newSheet;
96afb5c234S猫头猫    localSheetStateMapper.notify();
97afb5c234S猫头猫}
98afb5c234S猫头猫
99afb5c234S猫头猫function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null {
100afb5c234S猫头猫    const data = fn.slice(0, fn.lastIndexOf('.')).split('@');
101afb5c234S猫头猫    const [platform, id, title, artist] = data;
102afb5c234S猫头猫    if (!platform || !id) {
103afb5c234S猫头猫        return null;
104afb5c234S猫头猫    }
105afb5c234S猫头猫    return {
106afb5c234S猫头猫        id,
107*a8557e3dS猫头猫        platform: decodeURIComponent(platform),
108*a8557e3dS猫头猫        title: decodeURIComponent(title ?? ''),
109*a8557e3dS猫头猫        artist: decodeURIComponent(artist ?? ''),
110afb5c234S猫头猫    };
111afb5c234S猫头猫}
112afb5c234S猫头猫
113cd669353S猫头猫function localMediaFilter(_: FileStat) {
1148fc75cb2S猫头猫    return supportLocalMediaType.some(ext => _.filename.endsWith(ext));
115cd669353S猫头猫}
116cd669353S猫头猫
117806b2764S猫头猫let importToken: string | null = null;
118806b2764S猫头猫// 获取本地的文件列表
119806b2764S猫头猫async function getMusicStats(folderPaths: string[]) {
120806b2764S猫头猫    const _importToken = nanoid();
121806b2764S猫头猫    importToken = _importToken;
122806b2764S猫头猫    const musicList: FileStat[] = [];
123806b2764S猫头猫    let peek: string | undefined;
124806b2764S猫头猫    let dirFiles: FileStat[] = [];
125806b2764S猫头猫    while (folderPaths.length !== 0) {
126806b2764S猫头猫        if (importToken !== _importToken) {
127806b2764S猫头猫            throw new Error('Import Broken');
128806b2764S猫头猫        }
129806b2764S猫头猫        peek = folderPaths.shift() as string;
130cd669353S猫头猫        try {
131806b2764S猫头猫            dirFiles = await FileSystem.statDir(peek);
132806b2764S猫头猫        } catch {
133806b2764S猫头猫            dirFiles = [];
134806b2764S猫头猫        }
135cd669353S猫头猫
136806b2764S猫头猫        dirFiles.forEach(item => {
13722c09412S猫头猫            if (item.type === 'directory' && !folderPaths.includes(item.path)) {
138806b2764S猫头猫                folderPaths.push(item.path);
139cd669353S猫头猫            } else if (localMediaFilter(item)) {
140806b2764S猫头猫                musicList.push(item);
141cd669353S猫头猫            }
142806b2764S猫头猫        });
143cd669353S猫头猫    }
144806b2764S猫头猫    return {musicList, token: _importToken};
145806b2764S猫头猫}
146806b2764S猫头猫
147806b2764S猫头猫function cancelImportLocal() {
148806b2764S猫头猫    importToken = null;
149cd669353S猫头猫}
150cd669353S猫头猫
151cd669353S猫头猫// 导入本地音乐
152bb9b2f7bS猫头猫const groupNum = 50;
153806b2764S猫头猫async function importLocal(_folderPaths: string[]) {
154806b2764S猫头猫    const folderPaths = [..._folderPaths];
155806b2764S猫头猫    const {musicList, token} = await getMusicStats(folderPaths);
156806b2764S猫头猫    if (token !== importToken) {
157806b2764S猫头猫        throw new Error('Import Broken');
158806b2764S猫头猫    }
1598423b2d4S猫头猫    // 分组请求,不然序列化可能出问题
1608423b2d4S猫头猫    let metas: IBasicMeta[] = [];
1618423b2d4S猫头猫    const groups = Math.ceil(musicList.length / groupNum);
1628423b2d4S猫头猫    for (let i = 0; i < groups; ++i) {
1638423b2d4S猫头猫        metas = metas.concat(
1648423b2d4S猫头猫            await mp3Util.getMediaMeta(
1658423b2d4S猫头猫                musicList
1668423b2d4S猫头猫                    .slice(i * groupNum, (i + 1) * groupNum)
1678423b2d4S猫头猫                    .map(_ => _.path),
1688423b2d4S猫头猫            ),
1698423b2d4S猫头猫        );
1708423b2d4S猫头猫    }
171806b2764S猫头猫    if (token !== importToken) {
172806b2764S猫头猫        throw new Error('Import Broken');
173806b2764S猫头猫    }
174cd669353S猫头猫    const musicItems = await Promise.all(
175cd669353S猫头猫        musicList.map(async (musicStat, index) => {
176cd669353S猫头猫            let {platform, id, title, artist} =
177cd669353S猫头猫                parseFilename(musicStat.filename) ?? {};
178cd669353S猫头猫            const meta = metas[index];
179cd669353S猫头猫            if (!platform || !id) {
180cd669353S猫头猫                platform = '本地';
181cd669353S猫头猫                id = await FileSystem.hash(musicStat.path, 'MD5');
182cd669353S猫头猫            }
183cd669353S猫头猫            return {
184cd669353S猫头猫                id,
185cd669353S猫头猫                platform,
186cd669353S猫头猫                title: title ?? meta?.title ?? musicStat.filename,
187cd669353S猫头猫                artist: artist ?? meta?.artist ?? '未知歌手',
188cd669353S猫头猫                duration: parseInt(meta?.duration ?? '0') / 1000,
189cd669353S猫头猫                album: meta?.album ?? '未知专辑',
190cd669353S猫头猫                artwork: '',
191cd669353S猫头猫                [internalSerializeKey]: {
192cd669353S猫头猫                    localPath: musicStat.path,
193cd669353S猫头猫                },
194cd669353S猫头猫            };
195cd669353S猫头猫        }),
196cd669353S猫头猫    );
197806b2764S猫头猫    if (token !== importToken) {
198806b2764S猫头猫        throw new Error('Import Broken');
199806b2764S猫头猫    }
200cd669353S猫头猫    addMusic(musicItems);
201cd669353S猫头猫}
202cd669353S猫头猫
2030e4173cdS猫头猫/** 是否为本地音乐 */
2040e4173cdS猫头猫function isLocalMusic(
2050e4173cdS猫头猫    musicItem: ICommon.IMediaBase | null,
2060e4173cdS猫头猫): IMusic.IMusicItem | undefined {
2070e4173cdS猫头猫    return musicItem
2080e4173cdS猫头猫        ? localSheet.find(_ => isSameMediaItem(_, musicItem))
2090e4173cdS猫头猫        : undefined;
2100e4173cdS猫头猫}
2110e4173cdS猫头猫
2120e4173cdS猫头猫/** 状态-是否为本地音乐 */
2130e4173cdS猫头猫function useIsLocal(musicItem: IMusic.IMusicItem | null) {
2140e4173cdS猫头猫    const localMusicState = localSheetStateMapper.useMappedState();
2150e4173cdS猫头猫    const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem));
2160e4173cdS猫头猫    useEffect(() => {
2170e4173cdS猫头猫        if (!musicItem) {
2180e4173cdS猫头猫            setIsLocal(false);
2190e4173cdS猫头猫        } else {
2200e4173cdS猫头猫            setIsLocal(!!isLocalMusic(musicItem));
2210e4173cdS猫头猫        }
2220e4173cdS猫头猫    }, [localMusicState, musicItem]);
2230e4173cdS猫头猫    return isLocal;
2240e4173cdS猫头猫}
2250e4173cdS猫头猫
2260224b881S猫头猫function getMusicList() {
2270224b881S猫头猫    return localSheet;
2280224b881S猫头猫}
2290224b881S猫头猫
23054bb1cc8S猫头猫async function updateMusicList(newSheet: IMusic.IMusicItem[]) {
23154bb1cc8S猫头猫    const _localSheet = [...newSheet];
23254bb1cc8S猫头猫    try {
23354bb1cc8S猫头猫        await setStorage(StorageKeys.LocalMusicSheet, _localSheet);
23454bb1cc8S猫头猫        localSheet = _localSheet;
23554bb1cc8S猫头猫        localSheetStateMapper.notify();
23654bb1cc8S猫头猫    } catch {}
23754bb1cc8S猫头猫}
23854bb1cc8S猫头猫
2390e4173cdS猫头猫const LocalMusicSheet = {
2400e4173cdS猫头猫    setup,
2410e4173cdS猫头猫    addMusic,
2420e4173cdS猫头猫    removeMusic,
243dc160d50S猫头猫    addMusicDraft,
244dc160d50S猫头猫    saveLocalSheet,
245cd669353S猫头猫    importLocal,
246806b2764S猫头猫    cancelImportLocal,
2470e4173cdS猫头猫    isLocalMusic,
2480e4173cdS猫头猫    useIsLocal,
2490224b881S猫头猫    getMusicList,
2500e4173cdS猫头猫    useMusicList: localSheetStateMapper.useMappedState,
25154bb1cc8S猫头猫    updateMusicList,
2520e4173cdS猫头猫};
2530e4173cdS猫头猫
2540e4173cdS猫头猫export default LocalMusicSheet;
255