From b1822f45845d9ee485f9e92b9d1cce641ba9a1c1 Mon Sep 17 00:00:00 2001 From: lyswhut Date: Tue, 18 May 2021 15:32:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=BD=AF=E4=BB=B6=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E8=BD=AF=E4=BB=B6=E5=B4=A9=E6=BA=83=E7=9A=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 2 + publish/changeLog.md | 8 +- src/components/common/ConfirmAlert.js | 36 ++++--- src/components/common/Dialog.js | 2 +- src/lang/en_us.json | 12 ++- src/lang/zh_cn.json | 8 +- src/navigation/components/PactModal.js | 49 +++------- src/screens/Home/Setting/Other/Log.js | 95 ++++++++++++++++++ src/screens/Home/Setting/Other/index.js | 2 + src/utils/errorHandle.js | 11 ++- src/utils/log.js | 124 +++++++++++++++++++++++- 11 files changed, 291 insertions(+), 58 deletions(-) create mode 100644 src/screens/Home/Setting/Other/Log.js diff --git a/index.js b/index.js index 8ca8e4c..683d7c7 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ // import '@/utils/log' import './shim' import '@/utils/errorHandle' +import { init as initLog } from '@/utils/log' import '@/config/globalData' import SplashScreen from 'react-native-splash-screen' import { init as initNavigation, navigations, showPactModal } from '@/navigation' @@ -25,6 +26,7 @@ console.log('starting app...') let store let isInited = false let isFirstRun = true +initLog() const init = () => { if (isInited) return Promise.resolve() diff --git a/publish/changeLog.md b/publish/changeLog.md index a738df3..2d08fd1 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -1,5 +1,7 @@ +### 优化 + +- 改进软件错误处理,添加对软件崩溃的错误日志记录,可在设置-其他查看错误日志历史。注:清理缓存时日志也将会被清理 + ### 修复 -- 修复修复协议弹窗可以被绕过的问题 -- 修复从在线列表使用稍后播放功能播放歌曲时,歌曲封面不显示的问题 -- 修复正在播放“稍后播放”的歌曲时,对“稍后播放”前播放的列表进行添加、删除操作会导致切歌的问题 +- 修复显示版本更新弹窗会导致应用崩溃的问题 diff --git a/src/components/common/ConfirmAlert.js b/src/components/common/ConfirmAlert.js index 6c70306..10655b9 100644 --- a/src/components/common/ConfirmAlert.js +++ b/src/components/common/ConfirmAlert.js @@ -10,12 +10,14 @@ const styles = StyleSheet.create({ // flexGrow: 0, flexShrink: 1, marginTop: 15, - marginLeft: 15, - marginRight: 15, + marginLeft: 5, + marginRight: 5, marginBottom: 25, }, content: { flexGrow: 0, + paddingLeft: 10, + paddingRight: 10, }, title: { fontSize: 14, @@ -24,9 +26,15 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', paddingBottom: 15, - paddingLeft: 15, // paddingRight: 15, }, + btnsDirection: { + paddingLeft: 15, + }, + btnsReversedDirection: { + paddingLeft: 15, + flexDirection: 'row-reverse', + }, btn: { flex: 1, paddingTop: 10, @@ -35,8 +43,13 @@ const styles = StyleSheet.create({ paddingRight: 10, alignItems: 'center', borderRadius: 4, + }, + btnDirection: { marginRight: 15, }, + btnReversedDirection: { + marginLeft: 15, + }, }) @@ -51,8 +64,9 @@ export default ({ text = '', cancelText = '', confirmText = '', - showCancel = true, + showConfirm = true, children, + reverseBtn = false, }) => { const theme = useGetter('common', 'theme') const { t } = useTranslation() @@ -64,15 +78,15 @@ export default ({ {children || {text}} - - {showCancel - ? + {showConfirm + ? : null} - ) diff --git a/src/components/common/Dialog.js b/src/components/common/Dialog.js index 7bc84b9..5d3b7b9 100644 --- a/src/components/common/Dialog.js +++ b/src/components/common/Dialog.js @@ -14,7 +14,7 @@ const styles = StyleSheet.create({ }, modalView: { maxWidth: '90%', - minWidth: '50%', + minWidth: '60%', maxHeight: '78%', backgroundColor: 'white', borderRadius: 4, diff --git a/src/lang/en_us.json b/src/lang/en_us.json index 1e37009..d6bbf60 100644 --- a/src/lang/en_us.json +++ b/src/lang/en_us.json @@ -168,11 +168,17 @@ "setting_backup_part_import_list_desc": "Select the list of backup files", "setting_backup_part_export_list_desc": "Select the save location of the playlist backup file", - "setting_other": "Extras", - "setting_other_cache": "Cache management (including the cache of songs, lyrics, etc., it is not recommended to clean up if there is no problem related to song playback)", - "setting_other_cache_size": "Currently used cache size: ", + "setting_other": "Other", + "setting_other_cache": "Cache management (including the cache of songs, lyrics, error logs, etc., it is not recommended to clean up if there is no problem related to song playback)", + "setting_other_cache_size": "Currently used cache size:", "setting_other_cache_clear_btn": "Clear Cache", "setting_other_cache_clear_success_tip": "Cache clearing completed", + "setting_other_log": "Error log (error log when the software crashes 💥)", + "setting_other_log_tip_clean_success": "Log cleaning completed", + "setting_other_log_tip_null": "The log is empty~", + "setting_other_log_btn_show": "View log", + "setting_other_log_btn_hide": "Close", + "setting_other_log_btn_clean": "Clear", "setting_version": "Software Update", "setting_version_show_ver_modal": "Open the update window 🚀", diff --git a/src/lang/zh_cn.json b/src/lang/zh_cn.json index 06ae30d..0896906 100644 --- a/src/lang/zh_cn.json +++ b/src/lang/zh_cn.json @@ -169,10 +169,16 @@ "setting_backup_part_export_list_desc": "选择歌单备份文件保存位置", "setting_other": "其他", - "setting_other_cache": "缓存管理(包括歌曲、歌词等缓存,没有歌曲播放相关的问题不建议清理)", + "setting_other_cache": "缓存管理(包括歌曲、歌词、错误日志等缓存,没有歌曲播放相关的问题不建议清理)", "setting_other_cache_size": "当前已用缓存大小:", "setting_other_cache_clear_btn": "清理缓存", "setting_other_cache_clear_success_tip": "缓存清理完成", + "setting_other_log": "错误日志(软件崩溃💥时的错误日志)", + "setting_other_log_tip_clean_success": "日志清理完成", + "setting_other_log_tip_null": "日志是空的哦~", + "setting_other_log_btn_show": "查看日志", + "setting_other_log_btn_hide": "关闭", + "setting_other_log_btn_clean": "清空", "setting_version": "软件更新", "setting_version_show_ver_modal": "打开更新窗口 🚀", diff --git a/src/navigation/components/PactModal.js b/src/navigation/components/PactModal.js index 6f4648d..d28412b 100644 --- a/src/navigation/components/PactModal.js +++ b/src/navigation/components/PactModal.js @@ -34,6 +34,7 @@ const VersionModal = ({ componentId }) => { const textStyle = StyleSheet.compose(styles.text, { color: theme.normal, + marginBottom: 10, }) const textLinkStyle = StyleSheet.compose(styles.text, { textDecorationLine: 'underline', @@ -74,46 +75,22 @@ const VersionModal = ({ componentId }) => { 许可协议 - - 本项目(软件)基于 Apache License 2.0 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。 - - - 词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。 - - - 1. 本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本软件不对数据的准确性负责。 - - - 2. 使用本软件的过程中可能会产生版权数据,对于这些版权数据,本软件不拥有它们的所有权,为了避免造成侵权,使用者务必在 24小时内 清除使用本软件的过程中所产生的版权数据。 - - - 3. 本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本软件更改或移除。 - - - 4. 本软件内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本软件移除。 - - - 5. 由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。 - - - 6. 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,禁止在违反当地法律法规的情况下使用本软件,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。 - - - * 若协议更新,恕不另行通知,可到开源地址查看。 - - - * 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。 - - - * 音乐平台不易,建议到对应音乐平台支持正版资源。 - + 本项目(软件)基于 Apache License 2.0 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。 + 词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。 + 1. 本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本软件不对数据的准确性负责。 + 2. 使用本软件的过程中可能会产生版权数据,对于这些版权数据,本软件不拥有它们的所有权,为了避免造成侵权,使用者务必在 24小时内 清除使用本软件的过程中所产生的版权数据。 + 3. 本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本软件更改或移除。 + 4. 本软件内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本软件移除。 + 5. 由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。 + 6. 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,禁止在违反当地法律法规的情况下使用本软件,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。 + * 若协议更新,恕不另行通知,可到开源地址查看。 + * 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。 + * 音乐平台不易,建议到对应音乐平台支持正版资源。 { isAgreePact ? null : ( - - 若你(使用者)接受以上协议,请点击下面的“接受”按钮签署本协议,若不接受,请点击“不接受”后退出软件并清除本软件的所有数据。 - + 若你(使用者)接受以上协议,请点击下面的“接受”按钮签署本协议,若不接受,请点击“不接受”后退出软件并清除本软件的所有数据。 ) } diff --git a/src/screens/Home/Setting/Other/Log.js b/src/screens/Home/Setting/Other/Log.js new file mode 100644 index 0000000..29ad5fa --- /dev/null +++ b/src/screens/Home/Setting/Other/Log.js @@ -0,0 +1,95 @@ +import React, { memo, useRef, useState, useEffect } from 'react' +import { StyleSheet, View, Text, InteractionManager } from 'react-native' +import { LOG_TYPE, getLogs, clearLogs } from '@/utils/log' +import { useGetter } from '@/store' +// import { gzip, ungzip } from 'pako' + +import SubTitle from '../components/SubTitle' +import Button from '../components/Button' +import { useTranslation } from '@/plugins/i18n' +import { toast } from '@/utils/tools' +import ConfirmAlert from '@/components/common/ConfirmAlert' + +export default memo(() => { + const { t } = useTranslation() + const [visibleNewFolder, setVisibleNewFolder] = useState(false) + const [logText, setLogText] = useState('') + const theme = useGetter('common', 'theme') + const isUnmountedRef = useRef(true) + + const getErrorLog = () => { + getLogs(LOG_TYPE.error).then(log => { + if (isUnmountedRef.current) return + const logArr = log.split('\n') + logArr.reverse() + setLogText(logArr.join('\n\n').replace(/\n+$/, '')) + }) + } + + const openLogModal = () => { + getErrorLog() + setVisibleNewFolder(true) + } + + const handleHide = () => { + setVisibleNewFolder(false) + } + + const handleCleanLog = () => { + clearLogs(LOG_TYPE.error).then(() => { + toast(t('setting_other_log_tip_clean_success')) + getErrorLog() + }) + } + + + useEffect(() => { + isUnmountedRef.current = false + return () => { + isUnmountedRef.current = true + } + // handleGetAppCacheSize() + }, []) + + return ( + <> + + + + + + + true}> + { + logText + ? { logText } + : {t('setting_other_log_tip_null')} + } + + + + ) +}) + +const styles = StyleSheet.create({ + cacheSize: { + marginBottom: 5, + }, + btn: { + flexDirection: 'row', + }, + tipText: { + fontSize: 14, + }, + logText: { + fontSize: 12, + }, +}) diff --git a/src/screens/Home/Setting/Other/index.js b/src/screens/Home/Setting/Other/index.js index 71fa21e..b14e4fc 100644 --- a/src/screens/Home/Setting/Other/index.js +++ b/src/screens/Home/Setting/Other/index.js @@ -2,6 +2,7 @@ import React, { memo } from 'react' import Section from '../components/Section' import Cache from './Cache' +import Log from './Log' // import MaxCache from './MaxCache' import { useTranslation } from '@/plugins/i18n' @@ -11,6 +12,7 @@ export default memo(() => { return (
+ {/* */}
) diff --git a/src/utils/errorHandle.js b/src/utils/errorHandle.js index b32b167..9bb266f 100644 --- a/src/utils/errorHandle.js +++ b/src/utils/errorHandle.js @@ -1,7 +1,11 @@ import { Alert } from 'react-native' +import { exitApp } from '@/utils/tools' import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler' +import { log } from '@/utils/log' + const errorHandler = (e, isFatal) => { if (isFatal) { + log.error(e.message) Alert.alert( '💥Unexpected error occurred💥', ` @@ -12,15 +16,20 @@ ${isFatal ? 'Fatal:' : ''} ${e.name} ${e.message} `, [{ text: '关闭 (Close)', + onPress: () => { + exitApp() + }, }], ) } else { + log.error(e.message) console.log(e) // So that we can see it in the ADB logs in case of Android if needed } } -setJSExceptionHandler(errorHandler) +setJSExceptionHandler(errorHandler, true) setNativeExceptionHandler((errorString) => { + log.error(errorString) console.error('+++++', errorString, '+++++') }) diff --git a/src/utils/log.js b/src/utils/log.js index 5732e87..238b461 100644 --- a/src/utils/log.js +++ b/src/utils/log.js @@ -1,6 +1,125 @@ -import { requestStoragePermission } from '@/utils/common' -import { externalDirectoryPath, existsFile, writeFile, appendFile } from '@/utils/fs' +// import { requestStoragePermission } from '@/utils/common' +import { temporaryDirectoryPath, existsFile, writeFile, appendFile, mkdir, readFile, unlink } from '@/utils/fs' +export const LOG_TYPE = { + info: 'INFO', + warn: 'WARN', + error: 'ERROR', +} +const logDir = temporaryDirectoryPath + '/lx_logs' +const logPath = { + info: logDir + '/info.log', + warn: logDir + '/warn.log', + error: logDir + '/error.log', +} +const logTools = { + tempLog: { + info: [], + warn: [], + error: [], + }, + writeLog(type, msg) { + switch (type) { + case LOG_TYPE.info: + appendFile(logPath.info, '\n' + msg) + break + case LOG_TYPE.warn: + appendFile(logPath.warn, '\n' + msg) + break + case LOG_TYPE.error: + appendFile(logPath.error, '\n' + msg) + break + default: + break + } + }, + async initLogFile(type, filePath) { + try { + let isExists = await existsFile(filePath) + if (!isExists) await writeFile(filePath, '') + if (this.tempLog[type].length) this.writeLog(LOG_TYPE[type], this.tempLog[type].map(m => `${m.time} ${m.type} ${m.text}`).join('\n')) + this.tempLog[type] = null + } catch (err) { + console.error(err) + } + }, +} + +export const init = () => { + return mkdir(logDir).then(() => { + const tasks = [] + for (const [type, path] of Object.entries(logPath)) { + tasks.push(logTools.initLogFile(type, path)) + } + console.log('init log tools') + return Promise.all(tasks) + }) +} + +export const getLogs = (type = LOG_TYPE.error) => { + let path + switch (type) { + case LOG_TYPE.info: + path = logPath.info + break + case LOG_TYPE.warn: + path = logPath.warn + break + case LOG_TYPE.error: + path = logPath.error + break + default: + return Promise.reject(new Error('Unknow log type')) + } + return readFile(path) +} + +export const clearLogs = (type = LOG_TYPE.error) => { + let path + switch (type) { + case LOG_TYPE.info: + path = logPath.info + break + case LOG_TYPE.warn: + path = logPath.warn + break + case LOG_TYPE.error: + path = logPath.error + break + default: + return Promise.reject(new Error('Unknow log type')) + } + return unlink(path).then(() => writeFile(path, '')) +} + +export const log = { + info(...msgs) { + console.info(...msgs) + let msg = msgs.map(m => typeof m == 'object' ? JSON.stringify(m) : m).join(' ') + if (msg.startsWith('%c')) return + let time = new Date().toLocaleString() + if (logTools.tempLog.info) { + logTools.tempLog.info.push({ type: 'LOG', time, text: msg }) + } else logTools.writeLog(LOG_TYPE.info, `${time} LOG ${msg}`) + }, + warn(...msgs) { + console.warn(...msgs) + let msg = msgs.map(m => typeof m == 'object' ? JSON.stringify(m) : m).join(' ') + let time = new Date().toLocaleString() + if (logTools.tempLog.warn) { + logTools.tempLog.warn.push({ type: 'WARN', time, text: msg }) + } else logTools.writeLog(LOG_TYPE.warn, `${time} WARN ${msg}`) + }, + error(...msgs) { + console.error(...msgs) + let msg = msgs.map(m => typeof m == 'object' ? JSON.stringify(m) : m).join(' ') + let time = new Date().toLocaleString() + if (logTools.tempLog.error) { + logTools.tempLog.error.push({ type: 'ERROR', time, text: msg }) + } else logTools.writeLog(LOG_TYPE.error, `${time} ERROR ${msg}`) + }, +} +/* if (process.env.NODE_ENV !== 'development') { const logPath = externalDirectoryPath + '/debug.log' @@ -56,3 +175,4 @@ if (process.env.NODE_ENV !== 'development') { init() } + */