diff --git a/src/App.tsx b/src/App.tsx index 9a2deee..d15eaab 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import LibraryView from './components/library/LibraryView'; import PlayerBar from './components/player/PlayerBar'; import AlbumView from './components/library/AlbumView'; import ArtistView from './components/library/ArtistView'; +import setDefaultSettings from './components/shared/setDefaultSettings'; const keyMap = { FOCUS_SEARCH: 'ctrl+f', @@ -48,6 +49,9 @@ const App = () => { FOCUS_SEARCH: focusSearchInput, }; + // Always check for default settings + setDefaultSettings(false); + return ( diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx index 702bc24..f17ef07 100644 --- a/src/components/player/Player.tsx +++ b/src/components/player/Player.tsx @@ -24,6 +24,7 @@ import { } from '../../redux/playQueueSlice'; import { setCurrentSeek } from '../../redux/playerSlice'; import cacheSong from '../shared/cacheSong'; +import { getSongCachePath } from '../../shared/utils'; const Player = ({ children }: any, ref: any) => { const player1Ref = useRef(); @@ -254,14 +255,9 @@ const Player = ({ children }: any, ref: any) => { }; const checkCachedSong = (id: string) => { - const rootCacheFolder = path.join( - path.dirname(settings.file()), - 'sonixdCache', - `${settings.getSync('serverBase64')}` - ); - const songCacheFolder = path.join(rootCacheFolder, 'song'); + const songCacheFolder = getSongCachePath(); - const songCache = fs.readdirSync(songCacheFolder); + const songCache = fs.readdirSync(songCacheFolder || ''); const matchedSong = songCache.filter((song) => song.split('.')[0] === id); if (matchedSong.length !== 0) { diff --git a/src/components/settings/Config.tsx b/src/components/settings/Config.tsx index 72fb934..b6f219f 100644 --- a/src/components/settings/Config.tsx +++ b/src/components/settings/Config.tsx @@ -1,7 +1,21 @@ import React, { useEffect, useState } from 'react'; +import fs from 'fs'; import path from 'path'; import settings from 'electron-settings'; -import { Button, ControlLabel, InputNumber, Checkbox, Tag, Nav } from 'rsuite'; +import { + Button, + ControlLabel, + InputNumber, + Checkbox, + Tag, + Nav, + Icon, + Input, + InputGroup, + Message, + Whisper, + Popover, +} from 'rsuite'; import { ConfigPanel } from './styled'; import { startScan, getScanStatus } from '../../api/api'; import GenericPage from '../layout/GenericPage'; @@ -16,6 +30,9 @@ import { playlistColumnList, playlistColumnPicker, } from './ListViewColumns'; +import { getImageCachePath, getSongCachePath } from '../../shared/utils'; +import setDefaultSettings from '../shared/setDefaultSettings'; +import { HeaderButton } from '../shared/styled'; const fsUtils = require('nodejs-fs-utils'); @@ -25,6 +42,9 @@ const Config = () => { const [imgCacheSize, setImgCacheSize] = useState(0); const [songCacheSize, setSongCacheSize] = useState(0); const [currentLAFTab, setCurrentLAFTab] = useState('songList'); + const [isEditingCachePath, setIsEditingCachePath] = useState(false); + const [newCachePath, setNewCachePath] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); const songCols: any = settings.getSync('songListColumns'); const albumCols: any = settings.getSync('albumListColumns'); @@ -37,22 +57,22 @@ const Config = () => { useEffect(() => { // Retrieve cache sizes on render - const rootCacheFolder = path.join( - path.dirname(settings.file()), - 'sonixdCache', - `${settings.getSync('serverBase64')}` - ); - - const imgCacheFolder = path.join(rootCacheFolder, 'image'); - const songCacheFolder = path.join(rootCacheFolder, 'song'); + try { + setImgCacheSize( + Number( + (fsUtils.fsizeSync(getImageCachePath()) / 1000 / 1000).toFixed(0) + ) + ); - setImgCacheSize( - Number((fsUtils.fsizeSync(imgCacheFolder) / 1000 / 1000).toFixed(0)) - ); - - setSongCacheSize( - Number((fsUtils.fsizeSync(songCacheFolder) / 1000 / 1000).toFixed(0)) - ); + setSongCacheSize( + Number((fsUtils.fsizeSync(getSongCachePath()) / 1000 / 1000).toFixed(0)) + ); + } catch (err) { + setImgCacheSize(0); + setSongCacheSize(0); + fs.mkdirSync(getSongCachePath(), { recursive: true }); + fs.mkdirSync(getImageCachePath(), { recursive: true }); + } }, []); useEffect(() => { @@ -96,7 +116,8 @@ const Config = () => { title="Config" subtitle={ <> - + + +
+ Are you sure you want to reset your settings to default? +
+
+ +
+ + } + > + Reset defaults +
} sidetitle={} @@ -198,13 +244,66 @@ const Config = () => { )} -
- {path.join( - path.dirname(settings.file()), - 'sonixdCache', - `${settings.getSync('serverBase64')}` - )} -
+ {errorMessage !== '' && ( + <> + +
+ + )} +

+ Songs are cached only when playback for the track fully completes and + ends. Skipping to the next or previous track after only partially + completing the track will not begin the caching process. +

+
+ {isEditingCachePath && ( + <> + + setNewCachePath(e)} + /> + { + const check = fs.existsSync(newCachePath); + if (check) { + settings.setSync('cachePath', newCachePath); + fs.mkdirSync(getSongCachePath(), { recursive: true }); + fs.mkdirSync(getImageCachePath(), { recursive: true }); + setErrorMessage(''); + return setIsEditingCachePath(false); + } + + return setErrorMessage( + `Path: ${newCachePath} not found. Enter a valid path.` + ); + }} + > + + + { + setIsEditingCachePath(false); + setErrorMessage(''); + }} + > + + + +

+ *You will need to manually move any existing cached files to their + new location. +

+ + )} + {!isEditingCachePath && ( +
+ Location:{' '} + + {path.join(String(settings.getSync('cachePath')), 'sonixdCache')} + +
+ )}
{ settings.setSync('cacheSongs', !settings.getSync('cacheSongs')); }} > - Songs {songCacheSize} MB + Songs{' '} + + {songCacheSize} MB{' '} + {imgCacheSize === 9999999 && '- Folder not found'} + { settings.setSync('cacheImages', !settings.getSync('cacheImages')); }} > - Images {imgCacheSize} MB + Images{' '} + + {imgCacheSize} MB{' '} + {imgCacheSize === 9999999 && '- Folder not found'} + +
+
diff --git a/src/components/settings/ListViewColumns.ts b/src/components/settings/ListViewColumns.ts index fac09b9..a3b9485 100644 --- a/src/components/settings/ListViewColumns.ts +++ b/src/components/settings/ListViewColumns.ts @@ -245,7 +245,7 @@ export const albumColumnList = [ dataKey: 'songCount', alignment: 'center', resizable: true, - width: 70, + width: 100, label: 'Track Count', }, }, @@ -373,7 +373,7 @@ export const playlistColumnList = [ dataKey: 'changed', alignment: 'left', resizable: true, - width: 70, + width: 100, label: 'Modified', }, }, @@ -384,7 +384,7 @@ export const playlistColumnList = [ dataKey: 'owner', alignment: 'left', resizable: true, - width: 100, + width: 150, label: 'Owner', }, }, @@ -406,7 +406,7 @@ export const playlistColumnList = [ dataKey: 'songCount', alignment: 'center', resizable: true, - width: 70, + width: 100, label: 'Track Count', }, }, diff --git a/src/components/settings/Login.tsx b/src/components/settings/Login.tsx index bc05e55..db5c6f3 100644 --- a/src/components/settings/Login.tsx +++ b/src/components/settings/Login.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import fs from 'fs'; import md5 from 'md5'; import randomstring from 'randomstring'; import settings from 'electron-settings'; @@ -14,7 +13,7 @@ import { Message, } from 'rsuite'; import axios from 'axios'; -import { getImageCachePath, getSongCachePath } from '../../shared/utils'; +import setDefaultSettings from '../shared/setDefaultSettings'; const Login = () => { const [serverName, setServerName] = useState(''); @@ -51,6 +50,7 @@ const Login = () => { } localStorage.setItem('server', cleanServerName); + localStorage.setItem('serverBase64', btoa(cleanServerName)); localStorage.setItem('username', userName); localStorage.setItem('salt', salt); localStorage.setItem('hash', hash); @@ -61,127 +61,9 @@ const Login = () => { settings.setSync('salt', salt); settings.setSync('hash', hash); - // Create the cache folders - fs.mkdirSync(getSongCachePath(), { recursive: true }); - fs.mkdirSync(getImageCachePath(), { recursive: true }); + // Set defaults on login + setDefaultSettings(false); - // Set setting defaults on first login - if (!settings.hasSync('scrollWithCurrentSong')) { - settings.setSync('scrollWithCurrentSong', true); - } - - if (!settings.hasSync('cacheImages')) { - settings.setSync('cacheImages', false); - } - - if (!settings.hasSync('cacheSongs')) { - settings.setSync('cacheSongs', false); - } - - if (!settings.hasSync('fadeDuration')) { - settings.setSync('fadeDuration', '5.0'); - } - - if (!settings.hasSync('playlistViewType')) { - settings.setSync('playlistViewType', 'list'); - } - - if (!settings.hasSync('albumViewType')) { - settings.setSync('albumViewType', 'list'); - } - - if (!settings.hasSync('songListFontSize')) { - settings.setSync('songListFontSize', '14'); - } - - if (!settings.hasSync('songListRowHeight')) { - settings.setSync('songListRowHeight', '60.0'); - } - - if (!settings.hasSync('songListColumns')) { - settings.setSync('songListColumns', [ - { - id: '#', - dataKey: 'index', - alignment: 'center', - resizable: true, - width: 50, - label: '#', - }, - { - id: 'Title', - dataKey: 'combinedtitle', - alignment: 'left', - resizable: true, - width: 350, - label: 'Title (Combined)', - }, - { - id: 'Album', - dataKey: 'album', - alignment: 'left', - resizable: true, - width: 350, - label: 'Album', - }, - { - id: 'Duration', - dataKey: 'duration', - alignment: 'center', - resizable: true, - width: 100, - label: 'Duration', - }, - ]); - } - - if (!settings.hasSync('albumListFontSize')) { - settings.setSync('albumListFontSize', '14'); - } - - if (!settings.hasSync('albumListRowHeight')) { - settings.setSync('albumListRowHeight', '60.0'); - } - - if (!settings.hasSync('albumListColumns')) { - settings.setSync('albumListColumns', [ - { - id: '#', - dataKey: 'index', - alignment: 'center', - resizable: true, - width: 50, - label: '#', - }, - { - id: 'Title', - dataKey: 'combinedtitle', - alignment: 'left', - resizable: true, - width: 350, - label: 'Title (Combined)', - }, - { - label: 'Track Count', - value: { - id: 'Tracks', - dataKey: 'songCount', - alignment: 'center', - resizable: true, - width: 70, - label: 'Track Count', - }, - }, - { - id: 'Duration', - dataKey: 'duration', - alignment: 'center', - resizable: true, - width: 100, - label: 'Duration', - }, - ]); - } window.location.reload(); }; diff --git a/src/components/shared/cacheImage.ts b/src/components/shared/cacheImage.ts index 6a158f4..733222c 100644 --- a/src/components/shared/cacheImage.ts +++ b/src/components/shared/cacheImage.ts @@ -1,29 +1,17 @@ -import settings from 'electron-settings'; import fs from 'fs'; import path from 'path'; +import { getImageCachePath } from '../../shared/utils'; const download = require('image-downloader'); const cacheImage = (fileName: string, url: string) => { - const settingsPath = path.dirname(settings.file()); + const cachePath = getImageCachePath(); // We save the img to a temp path first so that React does not try to use the // in-progress downloaded image which would cause the image to be cut off - const tempImgPath = path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'image', - `TEMP_${fileName}` - ); + const tempImgPath = path.join(cachePath, `TEMP_${fileName}`); - const cachedImgPath = path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'image', - `${fileName}` - ); + const cachedImgPath = path.join(cachePath, `${fileName}`); const options = { url, @@ -31,25 +19,8 @@ const cacheImage = (fileName: string, url: string) => { }; // Create the cache folder if it doesn't exist - if ( - !fs.existsSync( - path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'image' - ) - ) - ) { - fs.mkdirSync( - path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'image' - ), - { recursive: true } - ); + if (!fs.existsSync(path.join(cachePath, 'image'))) { + fs.mkdirSync(path.join(cachePath, 'image'), { recursive: true }); } // Check if an existing cached image exists diff --git a/src/components/shared/cacheSong.ts b/src/components/shared/cacheSong.ts index 3c72a29..6a80d25 100644 --- a/src/components/shared/cacheSong.ts +++ b/src/components/shared/cacheSong.ts @@ -1,30 +1,18 @@ -import settings from 'electron-settings'; import fs from 'fs'; import path from 'path'; +import { getSongCachePath } from '../../shared/utils'; // We can re-use the image downloader package for song caching const download = require('image-downloader'); const cacheSong = (fileName: string, url: string) => { - const settingsPath = path.dirname(settings.file()); + const cachePath = getSongCachePath(); // We save the song to a temp path first so that React does not try to use the // in-progress downloaded image which would cause the image to be cut off - const tempSongPath = path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'song', - `TEMP_${fileName}` - ); + const tempSongPath = path.join(cachePath, `TEMP_${fileName}`); - const cachedSongPath = path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'song', - `${fileName}` - ); + const cachedSongPath = path.join(cachePath, `${fileName}`); const options = { url, @@ -32,25 +20,8 @@ const cacheSong = (fileName: string, url: string) => { }; // Create the cache folder if it doesn't exist - if ( - !fs.existsSync( - path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'song' - ) - ) - ) { - fs.mkdirSync( - path.join( - settingsPath, - 'sonixdCache', - `${settings.getSync('serverBase64')}`, - 'song' - ), - { recursive: true } - ); + if (!fs.existsSync(path.join(cachePath, 'song'))) { + fs.mkdirSync(path.join(cachePath, 'song'), { recursive: true }); } // Check if an existing cached image exists diff --git a/src/components/shared/setDefaultSettings.ts b/src/components/shared/setDefaultSettings.ts new file mode 100644 index 0000000..0282477 --- /dev/null +++ b/src/components/shared/setDefaultSettings.ts @@ -0,0 +1,221 @@ +import settings from 'electron-settings'; +import path from 'path'; +import fs from 'fs'; +import { getSongCachePath, getImageCachePath } from '../../shared/utils'; +// Set setting defaults on first login +const setDefaultSettings = (force: boolean) => { + if (force || !settings.hasSync('cachePath')) { + settings.setSync( + 'cachePath', + path.join( + path.dirname(settings.file()), + 'sonixdCache', + `${localStorage.getItem('serverBase64')}` + ) + ); + } + + fs.mkdirSync(getSongCachePath(), { recursive: true }); + fs.mkdirSync(getImageCachePath(), { recursive: true }); + + if (force || !settings.hasSync('scrollWithCurrentSong')) { + settings.setSync('scrollWithCurrentSong', true); + } + + if (force || !settings.hasSync('cacheImages')) { + settings.setSync('cacheImages', false); + } + + if (force || !settings.hasSync('cacheSongs')) { + settings.setSync('cacheSongs', false); + } + + if (force || !settings.hasSync('fadeDuration')) { + settings.setSync('fadeDuration', '5.0'); + } + + if (force || !settings.hasSync('playlistViewType')) { + settings.setSync('playlistViewType', 'list'); + } + + if (force || !settings.hasSync('albumViewType')) { + settings.setSync('albumViewType', 'list'); + } + + if (force || !settings.hasSync('songListFontSize')) { + settings.setSync('songListFontSize', '14'); + } + + if (force || !settings.hasSync('songListRowHeight')) { + settings.setSync('songListRowHeight', '60.0'); + } + + if (force || !settings.hasSync('songListColumns')) { + settings.setSync('songListColumns', [ + { + id: '#', + dataKey: 'index', + alignment: 'center', + resizable: true, + width: 50, + label: '#', + }, + { + id: 'Title', + dataKey: 'combinedtitle', + alignment: 'left', + resizable: true, + width: 350, + label: 'Title (Combined)', + }, + { + id: 'Album', + dataKey: 'album', + alignment: 'left', + resizable: true, + width: 350, + label: 'Album', + }, + { + id: 'Duration', + dataKey: 'duration', + alignment: 'center', + resizable: true, + width: 100, + label: 'Duration', + }, + { + id: 'Fav', + dataKey: 'starred', + alignment: 'center', + resizable: true, + width: 60, + label: 'Favorite', + }, + ]); + } + + if (force || !settings.hasSync('albumListFontSize')) { + settings.setSync('albumListFontSize', '14'); + } + + if (force || !settings.hasSync('albumListRowHeight')) { + settings.setSync('albumListRowHeight', '60.0'); + } + + if (force || !settings.hasSync('albumListColumns')) { + settings.setSync('albumListColumns', [ + { + id: '#', + dataKey: 'index', + alignment: 'center', + resizable: true, + width: 50, + label: '#', + }, + { + id: 'Title', + dataKey: 'combinedtitle', + alignment: 'left', + resizable: true, + width: 350, + label: 'Title (Combined)', + }, + { + id: 'Tracks', + dataKey: 'songCount', + alignment: 'center', + resizable: true, + width: 100, + label: 'Track Count', + }, + { + id: 'Duration', + dataKey: 'duration', + alignment: 'center', + resizable: true, + width: 100, + label: 'Duration', + }, + { + id: 'Fav', + dataKey: 'starred', + alignment: 'center', + resizable: true, + width: 60, + label: 'Favorite', + }, + ]); + } + + if (force || !settings.hasSync('playlistListFontSize')) { + settings.setSync('playlistListFontSize', '14'); + } + + if (force || !settings.hasSync('playlistListRowHeight')) { + settings.setSync('playlistListRowHeight', '40.0'); + } + + if (force || !settings.hasSync('playlistListColumns')) { + settings.setSync('playlistListColumns', [ + { + id: '#', + dataKey: 'index', + alignment: 'center', + resizable: true, + width: 50, + label: '#', + }, + { + id: 'Art', + dataKey: 'coverart', + alignment: 'center', + resizable: true, + width: 100, + label: 'CoverArt', + }, + { + id: 'Title', + dataKey: 'name', + alignment: 'left', + resizable: true, + width: 300, + label: 'Title', + }, + { + id: 'Description', + dataKey: 'comment', + alignment: 'left', + resizable: true, + width: 200, + label: 'Description', + }, + { + id: 'Tracks', + dataKey: 'songCount', + alignment: 'center', + resizable: true, + width: 100, + label: 'Track Count', + }, + { + id: 'Owner', + dataKey: 'owner', + alignment: 'left', + resizable: true, + width: 150, + label: 'Owner', + }, + { + id: 'Modified', + dataKey: 'changed', + alignment: 'left', + resizable: true, + width: 100, + label: 'Modified', + }, + ]); + } +}; + +export default setDefaultSettings; diff --git a/src/components/shared/styled.ts b/src/components/shared/styled.ts new file mode 100644 index 0000000..dd91138 --- /dev/null +++ b/src/components/shared/styled.ts @@ -0,0 +1,7 @@ +import { Button } from 'rsuite'; +import styled from 'styled-components'; + +export const HeaderButton = styled(Button)` + margin-left: 5px; + margin-right: 5px; +`; diff --git a/src/components/viewtypes/ListViewTable.tsx b/src/components/viewtypes/ListViewTable.tsx index 08d9ce3..39f5832 100644 --- a/src/components/viewtypes/ListViewTable.tsx +++ b/src/components/viewtypes/ListViewTable.tsx @@ -385,7 +385,9 @@ const ListViewTable = ({ textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflow: 'hidden', - paddingRight: !column.dataKey.match(/starred|songCount/) + paddingRight: !column.dataKey?.match( + /starred|songCount/ + ) ? '10px' : undefined, }} diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 6b78934..485eec2 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -9,7 +9,7 @@ export const isCached = (filePath: string) => { export const getRootCachePath = () => { return path.join( - path.dirname(settings.file()), + String(settings.getSync('cachePath')), 'sonixdCache', `${settings.getSync('serverBase64')}` );