xref: /MusicFree/src/core/localMusicSheet.ts (revision 806b27640bb516a8f62fbc5eeec7e25b9105dd8f)
10e4173cdS猫头猫import {internalSerializeKey, StorageKeys} from '@/constants/commonConst';
2*806b2764S猫头猫import mp3Util 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';
10*806b2764S猫头猫import {nanoid} from 'nanoid';
110e4173cdS猫头猫import {useEffect, useState} from 'react';
12cd669353S猫头猫import {FileStat, FileSystem} from 'react-native-file-access';
13afb5c234S猫头猫
14afb5c234S猫头猫let localSheet: IMusic.IMusicItem[] = [];
15afb5c234S猫头猫const localSheetStateMapper = new StateMapper(() => localSheet);
16afb5c234S猫头猫
17afb5c234S猫头猫export async function setup() {
1888772aabS猫头猫    const sheet = await getStorage(StorageKeys.LocalMusicSheet);
1988772aabS猫头猫    if (sheet) {
2088772aabS猫头猫        let validSheet = [];
2188772aabS猫头猫        for (let musicItem of sheet) {
2288772aabS猫头猫            const localPath = getInternalData<string>(
2388772aabS猫头猫                musicItem,
2488772aabS猫头猫                InternalDataType.LOCALPATH,
2588772aabS猫头猫            );
2688772aabS猫头猫            if (localPath && (await FileSystem.exists(localPath))) {
2788772aabS猫头猫                validSheet.push(musicItem);
2888772aabS猫头猫            }
2988772aabS猫头猫        }
3088772aabS猫头猫        if (validSheet.length !== sheet.length) {
3188772aabS猫头猫            await setStorage(StorageKeys.LocalMusicSheet, validSheet);
3288772aabS猫头猫        }
3388772aabS猫头猫        localSheet = validSheet;
34afb5c234S猫头猫    } else {
35afb5c234S猫头猫        await setStorage(StorageKeys.LocalMusicSheet, []);
36afb5c234S猫头猫    }
37afb5c234S猫头猫    localSheetStateMapper.notify();
38afb5c234S猫头猫}
39afb5c234S猫头猫
40afb5c234S猫头猫export async function addMusic(
41afb5c234S猫头猫    musicItem: IMusic.IMusicItem | IMusic.IMusicItem[],
42afb5c234S猫头猫) {
43afb5c234S猫头猫    if (!Array.isArray(musicItem)) {
44afb5c234S猫头猫        musicItem = [musicItem];
45afb5c234S猫头猫    }
46afb5c234S猫头猫    let newSheet = [...localSheet];
47afb5c234S猫头猫    musicItem.forEach(mi => {
48afb5c234S猫头猫        if (localSheet.findIndex(_ => isSameMediaItem(mi, _)) === -1) {
49afb5c234S猫头猫            newSheet.push(mi);
50afb5c234S猫头猫        }
51afb5c234S猫头猫    });
52afb5c234S猫头猫    await setStorage(StorageKeys.LocalMusicSheet, newSheet);
53afb5c234S猫头猫    localSheet = newSheet;
54afb5c234S猫头猫    localSheetStateMapper.notify();
55afb5c234S猫头猫}
56afb5c234S猫头猫
57afb5c234S猫头猫export async function removeMusic(
58afb5c234S猫头猫    musicItem: IMusic.IMusicItem,
59afb5c234S猫头猫    deleteOriginalFile = false,
60afb5c234S猫头猫) {
61afb5c234S猫头猫    const idx = localSheet.findIndex(_ => isSameMediaItem(_, musicItem));
62afb5c234S猫头猫    let newSheet = [...localSheet];
63afb5c234S猫头猫    if (idx !== -1) {
64afb5c234S猫头猫        newSheet.splice(idx, 1);
650e4173cdS猫头猫        if (deleteOriginalFile && musicItem[internalSerializeKey]?.localPath) {
660e4173cdS猫头猫            await FileSystem.unlink(musicItem[internalSerializeKey].localPath);
67afb5c234S猫头猫        }
68afb5c234S猫头猫    }
69afb5c234S猫头猫    localSheet = newSheet;
70afb5c234S猫头猫    localSheetStateMapper.notify();
71afb5c234S猫头猫}
72afb5c234S猫头猫
73afb5c234S猫头猫function parseFilename(fn: string): Partial<IMusic.IMusicItem> | null {
74afb5c234S猫头猫    const data = fn.slice(0, fn.lastIndexOf('.')).split('@');
75afb5c234S猫头猫    const [platform, id, title, artist] = data;
76afb5c234S猫头猫    if (!platform || !id) {
77afb5c234S猫头猫        return null;
78afb5c234S猫头猫    }
79afb5c234S猫头猫    return {
80afb5c234S猫头猫        id,
81afb5c234S猫头猫        platform,
82afb5c234S猫头猫        title,
83afb5c234S猫头猫        artist,
84afb5c234S猫头猫    };
85afb5c234S猫头猫}
86afb5c234S猫头猫
87cd669353S猫头猫function localMediaFilter(_: FileStat) {
88cd669353S猫头猫    return (
89cd669353S猫头猫        _.filename.endsWith('.mp3') ||
90cd669353S猫头猫        _.filename.endsWith('.flac') ||
91cd669353S猫头猫        _.filename.endsWith('.wma')
92cd669353S猫头猫    );
93cd669353S猫头猫}
94cd669353S猫头猫
95*806b2764S猫头猫let importToken: string | null = null;
96*806b2764S猫头猫// 获取本地的文件列表
97*806b2764S猫头猫async function getMusicStats(folderPaths: string[]) {
98*806b2764S猫头猫    const _importToken = nanoid();
99*806b2764S猫头猫    importToken = _importToken;
100*806b2764S猫头猫    const musicList: FileStat[] = [];
101*806b2764S猫头猫    let peek: string | undefined;
102*806b2764S猫头猫    let dirFiles: FileStat[] = [];
103*806b2764S猫头猫    while (folderPaths.length !== 0) {
104*806b2764S猫头猫        if (importToken !== _importToken) {
105*806b2764S猫头猫            throw new Error('Import Broken');
106*806b2764S猫头猫        }
107*806b2764S猫头猫        peek = folderPaths.shift() as string;
108cd669353S猫头猫        try {
109*806b2764S猫头猫            dirFiles = await FileSystem.statDir(peek);
110*806b2764S猫头猫        } catch {
111*806b2764S猫头猫            dirFiles = [];
112*806b2764S猫头猫        }
113cd669353S猫头猫
114*806b2764S猫头猫        dirFiles.forEach(item => {
115cd669353S猫头猫            if (item.type === 'directory') {
116*806b2764S猫头猫                folderPaths.push(item.path);
117cd669353S猫头猫            } else if (localMediaFilter(item)) {
118*806b2764S猫头猫                musicList.push(item);
119cd669353S猫头猫            }
120*806b2764S猫头猫        });
121cd669353S猫头猫    }
122*806b2764S猫头猫    return {musicList, token: _importToken};
123*806b2764S猫头猫}
124*806b2764S猫头猫
125*806b2764S猫头猫function cancelImportLocal() {
126*806b2764S猫头猫    importToken = null;
127cd669353S猫头猫}
128cd669353S猫头猫
129cd669353S猫头猫// 导入本地音乐
130*806b2764S猫头猫async function importLocal(_folderPaths: string[]) {
131*806b2764S猫头猫    const folderPaths = [..._folderPaths];
132*806b2764S猫头猫    const {musicList, token} = await getMusicStats(folderPaths);
133*806b2764S猫头猫    if (token !== importToken) {
134*806b2764S猫头猫        throw new Error('Import Broken');
135*806b2764S猫头猫    }
136cd669353S猫头猫    const metas = await mp3Util.getMediaMeta(musicList.map(_ => _.path));
137*806b2764S猫头猫    if (token !== importToken) {
138*806b2764S猫头猫        throw new Error('Import Broken');
139*806b2764S猫头猫    }
140cd669353S猫头猫    const musicItems = await Promise.all(
141cd669353S猫头猫        musicList.map(async (musicStat, index) => {
142cd669353S猫头猫            let {platform, id, title, artist} =
143cd669353S猫头猫                parseFilename(musicStat.filename) ?? {};
144cd669353S猫头猫            const meta = metas[index];
145cd669353S猫头猫            if (!platform || !id) {
146cd669353S猫头猫                platform = '本地';
147cd669353S猫头猫                id = await FileSystem.hash(musicStat.path, 'MD5');
148cd669353S猫头猫            }
149cd669353S猫头猫            return {
150cd669353S猫头猫                id,
151cd669353S猫头猫                platform,
152cd669353S猫头猫                title: title ?? meta?.title ?? musicStat.filename,
153cd669353S猫头猫                artist: artist ?? meta?.artist ?? '未知歌手',
154cd669353S猫头猫                duration: parseInt(meta?.duration ?? '0') / 1000,
155cd669353S猫头猫                album: meta?.album ?? '未知专辑',
156cd669353S猫头猫                artwork: '',
157cd669353S猫头猫                [internalSerializeKey]: {
158cd669353S猫头猫                    localPath: musicStat.path,
159cd669353S猫头猫                },
160cd669353S猫头猫            };
161cd669353S猫头猫        }),
162cd669353S猫头猫    );
163*806b2764S猫头猫    if (token !== importToken) {
164*806b2764S猫头猫        throw new Error('Import Broken');
165*806b2764S猫头猫    }
166cd669353S猫头猫    addMusic(musicItems);
167cd669353S猫头猫}
168cd669353S猫头猫
1690e4173cdS猫头猫/** 是否为本地音乐 */
1700e4173cdS猫头猫function isLocalMusic(
1710e4173cdS猫头猫    musicItem: ICommon.IMediaBase | null,
1720e4173cdS猫头猫): IMusic.IMusicItem | undefined {
1730e4173cdS猫头猫    return musicItem
1740e4173cdS猫头猫        ? localSheet.find(_ => isSameMediaItem(_, musicItem))
1750e4173cdS猫头猫        : undefined;
1760e4173cdS猫头猫}
1770e4173cdS猫头猫
1780e4173cdS猫头猫/** 状态-是否为本地音乐 */
1790e4173cdS猫头猫function useIsLocal(musicItem: IMusic.IMusicItem | null) {
1800e4173cdS猫头猫    const localMusicState = localSheetStateMapper.useMappedState();
1810e4173cdS猫头猫    const [isLocal, setIsLocal] = useState<boolean>(!!isLocalMusic(musicItem));
1820e4173cdS猫头猫    useEffect(() => {
1830e4173cdS猫头猫        if (!musicItem) {
1840e4173cdS猫头猫            setIsLocal(false);
1850e4173cdS猫头猫        } else {
1860e4173cdS猫头猫            setIsLocal(!!isLocalMusic(musicItem));
1870e4173cdS猫头猫        }
1880e4173cdS猫头猫    }, [localMusicState, musicItem]);
1890e4173cdS猫头猫    return isLocal;
1900e4173cdS猫头猫}
1910e4173cdS猫头猫
1920224b881S猫头猫function getMusicList() {
1930224b881S猫头猫    return localSheet;
1940224b881S猫头猫}
1950224b881S猫头猫
1960e4173cdS猫头猫const LocalMusicSheet = {
1970e4173cdS猫头猫    setup,
1980e4173cdS猫头猫    addMusic,
1990e4173cdS猫头猫    removeMusic,
200cd669353S猫头猫    importLocal,
201*806b2764S猫头猫    cancelImportLocal,
2020e4173cdS猫头猫    isLocalMusic,
2030e4173cdS猫头猫    useIsLocal,
2040224b881S猫头猫    getMusicList,
2050e4173cdS猫头猫    useMusicList: localSheetStateMapper.useMappedState,
2060e4173cdS猫头猫};
2070e4173cdS猫头猫
2080e4173cdS猫头猫export default LocalMusicSheet;
209