diff --git a/electron/index.js b/electron/index.js index 182a2d4f9..bd2602353 100644 --- a/electron/index.js +++ b/electron/index.js @@ -17,8 +17,12 @@ import startSandbox from './startSandbox'; import installDevtools from './installDevtools'; import fs from 'fs'; import path from 'path'; +import { diskSpaceLinux } from '../ui/util/diskspace'; + const { download } = require('electron-dl'); const remote = require('@electron/remote/main'); +const os = require('os'); + remote.initialize(); const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled'); let upgradeDisabled; @@ -292,6 +296,22 @@ app.on('before-quit', () => { appState.isQuitting = true; }); +ipcMain.on('get-disk-space', async (event) => { + try { + const { data_dir } = await Lbry.settings_get(); + if (os.platform() === 'linux') { + const stdout = await diskSpaceLinux(data_dir); + const dfResult = stdout.split('\n')[1].split(/\s+/); + const total_and_available = { total: dfResult[1], free: dfResult[3]}; + rendererWindow.webContents.send('send-disk-space', { diskSpace: total_and_available }); + } + // const space = await nodeDiskInfo.getDiskInfo(); + } catch (e) { + rendererWindow.webContents.send('send-disk-space', { error: e.message || e }); + console.log('Failed to start LbryFirst', e); + } +}); + ipcMain.on('download-upgrade', async (event, params) => { const { url, options } = params; const dir = fs.mkdtempSync(app.getPath('temp') + path.sep); diff --git a/flow-typed/Settings.js b/flow-typed/Settings.js index 89fc59118..f98fa2d99 100644 --- a/flow-typed/Settings.js +++ b/flow-typed/Settings.js @@ -6,3 +6,8 @@ declare type CommentServerDetails = { declare type WalletServerDetails = { }; + +declare type DiskSpace = { + total: string, + free: string, +}; diff --git a/static/app-strings.json b/static/app-strings.json index 831f549a8..0b8dc8edd 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2287,5 +2287,9 @@ "View History Hosting": "View History Hosting", "Disable automatic updates": "Disable automatic updates", "Preven't new updates to be downloaded automatically in the background (we will keep notifying you if there is an update)": "Preven't new updates to be downloaded automatically in the background (we will keep notifying you if there is an update)", + "Unlimited View Hosting": "Unlimited View Hosting", + "Choose View Hosting Limit": "Choose View Hosting Limit", + "View Hosting Limit (GB)": "View Hosting Limit (GB)", + "%free% of %total% available": "%free% of %total% available", "--end--": "--end--" } diff --git a/ui/component/settingDataHosting/index.js b/ui/component/settingDataHosting/index.js index 1bbdf3971..c001ae8d1 100644 --- a/ui/component/settingDataHosting/index.js +++ b/ui/component/settingDataHosting/index.js @@ -2,10 +2,12 @@ import { connect } from 'react-redux'; import { doSetDaemonSetting, doGetDaemonStatus, doCleanBlobs } from 'redux/actions/settings'; import { selectDaemonStatus, selectDaemonSettings } from 'redux/selectors/settings'; import SettingWalletServer from './view'; +import { selectDiskSpace } from 'redux/selectors/app'; const select = (state) => ({ daemonSettings: selectDaemonSettings(state), daemonStatus: selectDaemonStatus(state), + diskSpace: selectDiskSpace(state), }); const perform = (dispatch) => ({ diff --git a/ui/component/settingDataHosting/view.jsx b/ui/component/settingDataHosting/view.jsx index 035a32ef4..d2d05b458 100644 --- a/ui/component/settingDataHosting/view.jsx +++ b/ui/component/settingDataHosting/view.jsx @@ -5,6 +5,8 @@ import { FormField } from 'component/common/form'; import Button from 'component/button'; import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import { formatBytes } from 'util/format-bytes'; +import { isTrulyANumber } from 'util/number'; +import I18nMessage from 'component/i18nMessage'; const BYTES_PER_MB = 1048576; const ENABLE_AUTOMATIC_HOSTING = false; @@ -41,155 +43,168 @@ type Props = { daemonStatus: DaemonStatus, // --- perform --- setDaemonSetting: (string, ?SetDaemonSettingArg) => void, - cleanBlobs: () => void, + cleanBlobs: () => string, + diskSpace?: DiskSpace, + getDaemonStatus: () => void, }; -function SettingWalletServer(props: Props) { - const { daemonSettings, daemonStatus, setDaemonSetting, cleanBlobs } = props; +function SettingDataHosting(props: Props) { + const { daemonSettings, daemonStatus, setDaemonSetting, cleanBlobs, diskSpace, getDaemonStatus } = props; - const { disk_space } = daemonStatus; - const contentSpaceUsed = Number(disk_space.content_blobs_storage_used_mb); - const networkSpaceUsed = Number(disk_space.seed_blobs_storage_used_mb); - const blobLimitSetting = daemonSettings[DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB]; - const networkLimitSetting = daemonSettings[DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB]; + const { disk_space: blobSpace } = daemonStatus; + const contentSpaceUsed = Number(blobSpace.content_blobs_storage_used_mb); + const blobLimitSetting = daemonSettings[DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB] || '0'; const [contentBlobSpaceLimitGB, setContentBlobSpaceLimit] = React.useState( - blobLimitSetting ? blobLimitSetting / 1024 : 0 + blobLimitSetting ? String(blobLimitSetting / 1024) : '10' ); + const [applying, setApplying] = React.useState(false); + + const networkSpaceUsed = Number(blobSpace.seed_blobs_storage_used_mb); + const networkLimitSetting = daemonSettings[DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB] || '0'; const [networkBlobSpaceLimitGB, setNetworkBlobSpaceLimit] = React.useState( - networkLimitSetting ? networkLimitSetting / 1024 : 0 + networkLimitSetting ? String(networkLimitSetting / 1024) : '0' ); - const [limitSpace, setLimitSpace] = React.useState(Boolean(blobLimitSetting)); - function updateContentBlobLimitField(gb) { - if (gb === 0) { - setContentBlobSpaceLimit(0); - } else if (!gb || !isNaN(gb)) { - setContentBlobSpaceLimit(gb); - } + const [unlimited, setUnlimited] = React.useState(blobLimitSetting === '0'); + + React.useEffect(() => { + getDaemonStatus(); + }, []); + + function convertGbToMb(gb) { + return Number(gb) * 1024; } - function updateNetworkBlobLimitField(gb) { - if (gb === 0) { - setNetworkBlobSpaceLimit(0); - } else if (!gb || !isNaN(gb)) { - setNetworkBlobSpaceLimit(gb); - } - } - - function handleLimitSpace(value) { - setLimitSpace(value); - if (!value) { - setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, String(0)); + function handleContentLimitChange(gb) { + if (gb === '') { + setContentBlobSpaceLimit(''); + } else if (gb === '0') { + setContentBlobSpaceLimit('0.01'); // setting 0 means unlimited. } else { - const spaceLimitMB = contentBlobSpaceLimitGB * 1024; - setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, String(spaceLimitMB)); + if (isTrulyANumber(Number(gb))) { + setContentBlobSpaceLimit(gb); + } } } - function handleSetContentBlobSpaceLimit() { - const spaceLimitMB = contentBlobSpaceLimitGB * 1024; - if (!isNaN(spaceLimitMB) && blobLimitSetting !== spaceLimitMB * 1024) { - setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, String(spaceLimitMB)); + function handleNetworkLimitChange(gb) { + if (gb === '') { + setNetworkBlobSpaceLimit(''); + } else { + const numberGb = Number(gb); + if (isTrulyANumber(numberGb)) { + setNetworkBlobSpaceLimit(gb); + } } } - function handleSetNetworkBlobSpaceLimit() { - const spaceLimitMB = networkBlobSpaceLimitGB * 1024; - if (!isNaN(spaceLimitMB) && blobLimitSetting !== spaceLimitMB * 1024) { - setDaemonSetting(DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB, String(spaceLimitMB)); + async function handleApply() { + setApplying(true); + if (unlimited) { + await setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, '0'); + } else { + await setDaemonSetting( + DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, + String(contentBlobSpaceLimitGB === '0.01' ? '1' : convertGbToMb(contentBlobSpaceLimitGB)) + ); } + await setDaemonSetting( + DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB, + String(convertGbToMb(Number(networkBlobSpaceLimitGB))) + ); + await cleanBlobs(); + getDaemonStatus(); + setApplying(false); + } + + function validHostingAmount(amountString) { + const numberAmount = Number(amountString); + return amountString.length && ((numberAmount && String(numberAmount)) || numberAmount === 0); } return ( <> - +
setDaemonSetting('save_blobs', !daemonSettings.save_blobs)} checked={daemonSettings.save_blobs} label={__('Enable Data Hosting')} - /> - - {daemonSettings.save_blobs && ( - -
{__('View History Hosting')}
-
- {`View History Hosting using ${formatBytes(contentSpaceUsed * BYTES_PER_MB)} of ${ - daemonSettings[DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB] - ? formatBytes(daemonSettings[DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB] * BYTES_PER_MB) - : 'Unlimited' - }`} -
-
- )} - {daemonSettings.save_blobs && ( - - handleLimitSpace(!limitSpace)} - checked={limitSpace} - label={__('Limit Hosting of Content History')} - /> - - )} - - {daemonSettings.save_blobs && limitSpace && ( - updateContentBlobLimitField(e.target.value)} - value={contentBlobSpaceLimitGB} - inputButton={ -
+ {daemonSettings.save_blobs && ( +
+ setUnlimited(true)} + /> + setUnlimited(false)} + label={__('Choose View Hosting Limit')} + /> + {!unlimited && ( + e.preventDefault()} + label={__(`View Hosting Limit (GB)`)} + onChange={(e) => handleContentLimitChange(e.target.value)} + value={Number(contentBlobSpaceLimitGB) <= Number('0.01') ? '0' : contentBlobSpaceLimitGB} + /> + )} +
{`Currently using ${formatBytes(contentSpaceUsed * BYTES_PER_MB)}`}
+
)} - -