Browse Source

update settings

- add configurable cache location
- add global check for default settings
- add reset settings defaults button
- additional changes for default list columns
master
jeffvli 3 years ago
parent
commit
0258d5537f
  1. 4
      src/App.tsx
  2. 10
      src/components/player/Player.tsx
  3. 165
      src/components/settings/Config.tsx
  4. 8
      src/components/settings/ListViewColumns.ts
  5. 126
      src/components/settings/Login.tsx
  6. 41
      src/components/shared/cacheImage.ts
  7. 41
      src/components/shared/cacheSong.ts
  8. 221
      src/components/shared/setDefaultSettings.ts
  9. 7
      src/components/shared/styled.ts
  10. 4
      src/components/viewtypes/ListViewTable.tsx
  11. 2
      src/shared/utils.ts

4
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 (
<GlobalHotKeys keyMap={keyMap} handlers={handlers}>
<Router>

10
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<any>();
@ -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) {

165
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={
<>
<Button
<HeaderButton
size="sm"
onClick={async () => {
startScan();
setIsScanning(true);
@ -104,7 +125,32 @@ const Config = () => {
disabled={isScanning}
>
{isScanning ? `Scanning: ${scanProgress}` : 'Scan Library'}
</Button>
</HeaderButton>
<Whisper
trigger="click"
speaker={
<Popover title="Confirm">
<div>
Are you sure you want to reset your settings to default?
</div>
<div>
<Button
id="reset-submit-button"
size="sm"
onClick={() => {
setDefaultSettings(true);
window.location.reload();
}}
appearance="link"
>
Yes
</Button>
</div>
</Popover>
}
>
<HeaderButton size="sm">Reset defaults</HeaderButton>
</Whisper>
</>
}
sidetitle={<DisconnectButton />}
@ -198,13 +244,66 @@ const Config = () => {
)}
</ConfigPanel>
<ConfigPanel header="Cache" bordered>
<div style={{ overflow: 'auto' }}>
{path.join(
path.dirname(settings.file()),
'sonixdCache',
`${settings.getSync('serverBase64')}`
)}
</div>
{errorMessage !== '' && (
<>
<Message showIcon type="error" description={errorMessage} />
<br />
</>
)}
<p>
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.
</p>
<br />
{isEditingCachePath && (
<>
<InputGroup>
<Input
value={newCachePath}
onChange={(e: string) => setNewCachePath(e)}
/>
<InputGroup.Button
onClick={() => {
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.`
);
}}
>
<Icon icon="check" />
</InputGroup.Button>
<InputGroup.Button
onClick={() => {
setIsEditingCachePath(false);
setErrorMessage('');
}}
>
<Icon icon="close" />
</InputGroup.Button>
</InputGroup>
<p style={{ fontSize: 'smaller' }}>
*You will need to manually move any existing cached files to their
new location.
</p>
</>
)}
{!isEditingCachePath && (
<div style={{ overflow: 'auto' }}>
Location:{' '}
<code>
{path.join(String(settings.getSync('cachePath')), 'sonixdCache')}
</code>
</div>
)}
<div style={{ width: '300px', marginTop: '20px' }}>
<Checkbox
defaultChecked={Boolean(settings.getSync('cacheSongs'))}
@ -212,7 +311,11 @@ const Config = () => {
settings.setSync('cacheSongs', !settings.getSync('cacheSongs'));
}}
>
Songs <Tag>{songCacheSize} MB</Tag>
Songs{' '}
<Tag>
{songCacheSize} MB{' '}
{imgCacheSize === 9999999 && '- Folder not found'}
</Tag>
</Checkbox>
<Checkbox
defaultChecked={Boolean(settings.getSync('cacheImages'))}
@ -220,8 +323,16 @@ const Config = () => {
settings.setSync('cacheImages', !settings.getSync('cacheImages'));
}}
>
Images <Tag>{imgCacheSize} MB</Tag>
Images{' '}
<Tag>
{imgCacheSize} MB{' '}
{imgCacheSize === 9999999 && '- Folder not found'}
</Tag>
</Checkbox>
<br />
<Button onClick={() => setIsEditingCachePath(true)}>
Edit cache location
</Button>
</div>
</ConfigPanel>
</GenericPage>

8
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',
},
},

126
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();
};

41
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

41
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

221
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;

7
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;
`;

4
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,
}}

2
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')}`
);

Loading…
Cancel
Save