finish view data hosting

This commit is contained in:
zeppi 2022-02-25 15:01:14 -05:00 committed by jessopb
parent e9502410de
commit 04a6c735ac
12 changed files with 190 additions and 111 deletions

View file

@ -17,8 +17,12 @@ import startSandbox from './startSandbox';
import installDevtools from './installDevtools'; import installDevtools from './installDevtools';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { diskSpaceLinux } from '../ui/util/diskspace';
const { download } = require('electron-dl'); const { download } = require('electron-dl');
const remote = require('@electron/remote/main'); const remote = require('@electron/remote/main');
const os = require('os');
remote.initialize(); remote.initialize();
const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled'); const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled');
let upgradeDisabled; let upgradeDisabled;
@ -292,6 +296,22 @@ app.on('before-quit', () => {
appState.isQuitting = true; 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) => { ipcMain.on('download-upgrade', async (event, params) => {
const { url, options } = params; const { url, options } = params;
const dir = fs.mkdtempSync(app.getPath('temp') + path.sep); const dir = fs.mkdtempSync(app.getPath('temp') + path.sep);

View file

@ -6,3 +6,8 @@ declare type CommentServerDetails = {
declare type WalletServerDetails = { declare type WalletServerDetails = {
}; };
declare type DiskSpace = {
total: string,
free: string,
};

View file

@ -2287,5 +2287,9 @@
"View History Hosting": "View History Hosting", "View History Hosting": "View History Hosting",
"Disable automatic updates": "Disable automatic updates", "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)", "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--" "--end--": "--end--"
} }

View file

@ -2,10 +2,12 @@ import { connect } from 'react-redux';
import { doSetDaemonSetting, doGetDaemonStatus, doCleanBlobs } from 'redux/actions/settings'; import { doSetDaemonSetting, doGetDaemonStatus, doCleanBlobs } from 'redux/actions/settings';
import { selectDaemonStatus, selectDaemonSettings } from 'redux/selectors/settings'; import { selectDaemonStatus, selectDaemonSettings } from 'redux/selectors/settings';
import SettingWalletServer from './view'; import SettingWalletServer from './view';
import { selectDiskSpace } from 'redux/selectors/app';
const select = (state) => ({ const select = (state) => ({
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
daemonStatus: selectDaemonStatus(state), daemonStatus: selectDaemonStatus(state),
diskSpace: selectDiskSpace(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({

View file

@ -5,6 +5,8 @@ import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import * as DAEMON_SETTINGS from 'constants/daemon_settings';
import { formatBytes } from 'util/format-bytes'; import { formatBytes } from 'util/format-bytes';
import { isTrulyANumber } from 'util/number';
import I18nMessage from 'component/i18nMessage';
const BYTES_PER_MB = 1048576; const BYTES_PER_MB = 1048576;
const ENABLE_AUTOMATIC_HOSTING = false; const ENABLE_AUTOMATIC_HOSTING = false;
@ -41,155 +43,168 @@ type Props = {
daemonStatus: DaemonStatus, daemonStatus: DaemonStatus,
// --- perform --- // --- perform ---
setDaemonSetting: (string, ?SetDaemonSettingArg) => void, setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
cleanBlobs: () => void, cleanBlobs: () => string,
diskSpace?: DiskSpace,
getDaemonStatus: () => void,
}; };
function SettingWalletServer(props: Props) { function SettingDataHosting(props: Props) {
const { daemonSettings, daemonStatus, setDaemonSetting, cleanBlobs } = props; const { daemonSettings, daemonStatus, setDaemonSetting, cleanBlobs, diskSpace, getDaemonStatus } = props;
const { disk_space } = daemonStatus; const { disk_space: blobSpace } = daemonStatus;
const contentSpaceUsed = Number(disk_space.content_blobs_storage_used_mb); const contentSpaceUsed = Number(blobSpace.content_blobs_storage_used_mb);
const networkSpaceUsed = Number(disk_space.seed_blobs_storage_used_mb); const blobLimitSetting = daemonSettings[DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB] || '0';
const blobLimitSetting = daemonSettings[DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB];
const networkLimitSetting = daemonSettings[DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB];
const [contentBlobSpaceLimitGB, setContentBlobSpaceLimit] = React.useState( const [contentBlobSpaceLimitGB, setContentBlobSpaceLimit] = React.useState(
blobLimitSetting ? blobLimitSetting / 1024 : 0 blobLimitSetting ? String(blobLimitSetting / 1024) : '10'
); );
const [networkBlobSpaceLimitGB, setNetworkBlobSpaceLimit] = React.useState( const [applying, setApplying] = React.useState(false);
networkLimitSetting ? networkLimitSetting / 1024 : 0
);
const [limitSpace, setLimitSpace] = React.useState(Boolean(blobLimitSetting));
function updateContentBlobLimitField(gb) { const networkSpaceUsed = Number(blobSpace.seed_blobs_storage_used_mb);
if (gb === 0) { const networkLimitSetting = daemonSettings[DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB] || '0';
setContentBlobSpaceLimit(0); const [networkBlobSpaceLimitGB, setNetworkBlobSpaceLimit] = React.useState(
} else if (!gb || !isNaN(gb)) { networkLimitSetting ? String(networkLimitSetting / 1024) : '0'
);
const [unlimited, setUnlimited] = React.useState(blobLimitSetting === '0');
React.useEffect(() => {
getDaemonStatus();
}, []);
function convertGbToMb(gb) {
return Number(gb) * 1024;
}
function handleContentLimitChange(gb) {
if (gb === '') {
setContentBlobSpaceLimit('');
} else if (gb === '0') {
setContentBlobSpaceLimit('0.01'); // setting 0 means unlimited.
} else {
if (isTrulyANumber(Number(gb))) {
setContentBlobSpaceLimit(gb); setContentBlobSpaceLimit(gb);
} }
} }
}
function updateNetworkBlobLimitField(gb) { function handleNetworkLimitChange(gb) {
if (gb === 0) { if (gb === '') {
setNetworkBlobSpaceLimit(0); setNetworkBlobSpaceLimit('');
} else if (!gb || !isNaN(gb)) { } else {
const numberGb = Number(gb);
if (isTrulyANumber(numberGb)) {
setNetworkBlobSpaceLimit(gb); setNetworkBlobSpaceLimit(gb);
} }
} }
}
function handleLimitSpace(value) { async function handleApply() {
setLimitSpace(value); setApplying(true);
if (!value) { if (unlimited) {
setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, String(0)); await setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, '0');
} else { } else {
const spaceLimitMB = contentBlobSpaceLimitGB * 1024; await setDaemonSetting(
setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, String(spaceLimitMB)); 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 handleSetContentBlobSpaceLimit() { function validHostingAmount(amountString) {
const spaceLimitMB = contentBlobSpaceLimitGB * 1024; const numberAmount = Number(amountString);
if (!isNaN(spaceLimitMB) && blobLimitSetting !== spaceLimitMB * 1024) { return amountString.length && ((numberAmount && String(numberAmount)) || numberAmount === 0);
setDaemonSetting(DAEMON_SETTINGS.BLOB_STORAGE_LIMIT_MB, String(spaceLimitMB));
}
}
function handleSetNetworkBlobSpaceLimit() {
const spaceLimitMB = networkBlobSpaceLimitGB * 1024;
if (!isNaN(spaceLimitMB) && blobLimitSetting !== spaceLimitMB * 1024) {
setDaemonSetting(DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB, String(spaceLimitMB));
}
} }
return ( return (
<> <>
<fieldset-section> <div className={'fieldset-section'}>
<FormField <FormField
type="checkbox" type="checkbox"
name="save_blobs" name="save_blobs"
onChange={() => setDaemonSetting('save_blobs', !daemonSettings.save_blobs)} onChange={() => setDaemonSetting('save_blobs', !daemonSettings.save_blobs)}
checked={daemonSettings.save_blobs} checked={daemonSettings.save_blobs}
label={__('Enable Data Hosting')} label={__('Enable Data Hosting')}
helper={
diskSpace && (
<I18nMessage
tokens={{
free: formatBytes(Number(diskSpace.free) * 1024, 0),
total: formatBytes(Number(diskSpace.total) * 1024, 0),
}}
>
%free% of %total% available
</I18nMessage>
)
}
/> />
</fieldset-section>
{daemonSettings.save_blobs && (
<fieldset-section>
<div className={'settings__row-section-title'}>{__('View History Hosting')}</div>
<div className={'help'}>
{`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'
}`}
</div> </div>
</fieldset-section>
)}
{daemonSettings.save_blobs && ( {daemonSettings.save_blobs && (
<fieldset-section> <div className={'fieldset-section'}>
<FormField <FormField
type="checkbox" type="radio"
name="limit_space_used" name="no_hosting_limit"
onChange={() => handleLimitSpace(!limitSpace)} checked={unlimited}
checked={limitSpace} label={__('Unlimited View Hosting')}
label={__('Limit Hosting of Content History')} onChange={() => setUnlimited(true)}
/> />
</fieldset-section> <FormField
)} type="radio"
name="set_hosting_limit"
{daemonSettings.save_blobs && limitSpace && ( checked={!unlimited}
onChange={() => setUnlimited(false)}
label={__('Choose View Hosting Limit')}
/>
{!unlimited && (
<FormField <FormField
name="content_blob_limit_gb" name="content_blob_limit_gb"
type="text" type="number"
label={__(`Limit (GB)`)} min={0}
disabled={!daemonSettings.save_blobs} onWheel={(e) => e.preventDefault()}
onChange={(e) => updateContentBlobLimitField(e.target.value)} label={__(`View Hosting Limit (GB)`)}
value={contentBlobSpaceLimitGB} onChange={(e) => handleContentLimitChange(e.target.value)}
inputButton={ value={Number(contentBlobSpaceLimitGB) <= Number('0.01') ? '0' : contentBlobSpaceLimitGB}
<Button
disabled={isNaN(contentBlobSpaceLimitGB)}
button="primary"
label={__('Apply')}
onClick={handleSetContentBlobSpaceLimit}
/>
}
/> />
)} )}
<fieldset-section> <div className={'help'}>{`Currently using ${formatBytes(contentSpaceUsed * BYTES_PER_MB)}`}</div>
<Button type="button" button="inverse" onClick={cleanBlobs} label={__('Clean Now')} /> </div>
</fieldset-section>
{/* Automatic Hosting Section */}
{daemonSettings.save_blobs && ENABLE_AUTOMATIC_HOSTING && (
<fieldset-section>
<div className={'settings__row-section-title'}>{__('Automatic Hosting - Experimental')}</div>
<p className={'help'}>
{`Automatic Hosting using ${formatBytes(networkSpaceUsed * BYTES_PER_MB)} of ${formatBytes(
daemonSettings[DAEMON_SETTINGS.NETWORK_STORAGE_LIMIT_MB] * BYTES_PER_MB
)}`}
</p>
</fieldset-section>
)} )}
{daemonSettings.save_blobs && ENABLE_AUTOMATIC_HOSTING && ( {daemonSettings.save_blobs && ENABLE_AUTOMATIC_HOSTING && (
<> <fieldset-section>
<FormField <FormField
name="network_blob_limit_gb" name="network_blob_limit_gb"
type="text" type="number"
label={__(`Allow (GB)`)} label={__(`Automatic Hosting (GB)`)}
disabled={!daemonSettings.save_blobs} onChange={(e) => handleNetworkLimitChange(e.target.value)}
onChange={(e) => updateNetworkBlobLimitField(e.target.value)}
value={networkBlobSpaceLimitGB} value={networkBlobSpaceLimitGB}
inputButton={
<Button
disabled={isNaN(networkBlobSpaceLimitGB)}
button="primary"
label={__('Apply')}
onClick={handleSetNetworkBlobSpaceLimit}
/> />
} <div className={'help'}>{`Auto-hosting ${formatBytes(networkSpaceUsed * BYTES_PER_MB)}`}</div>
/> </fieldset-section>
<div className="form-field__help">{__('Download and serve arbitrary data on the network.')}</div>
</>
)} )}
<div className={'card__actions'}>
<Button
disabled={
(unlimited && blobLimitSetting === '0') ||
(!unlimited &&
(blobLimitSetting === convertGbToMb(contentBlobSpaceLimitGB) || // &&
// networkLimitSetting === convertGbToMb(networkBlobSpaceLimitGB)
!validHostingAmount(String(contentBlobSpaceLimitGB)))) ||
applying
}
type="button"
button="primary"
onClick={handleApply}
label={__('Apply')}
/>
</div>
</> </>
); );
} }
export default SettingWalletServer; export default SettingDataHosting;

View file

@ -32,6 +32,7 @@ export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION'; export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL'; export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
export const SET_INCOGNITO = 'SET_INCOGNITO'; export const SET_INCOGNITO = 'SET_INCOGNITO';
export const DISK_SPACE = 'DISK_SPACE';
// Navigation // Navigation
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH'; export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';

View file

@ -91,6 +91,18 @@ rewards.setCallback('claimRewardSuccess', (reward) => {
} }
}); });
ipcRenderer.on('send-disk-space', (event, result) => {
if (result.error) {
console.log(`disk space error: ${result.error}`);
} else {
app.store.dispatch({
type: ACTIONS.DISK_SPACE,
data: result.diskSpace,
});
}
});
ipcRenderer.send('get-disk-space');
// @if TARGET='app' // @if TARGET='app'
ipcRenderer.on('open-uri-requested', (event, url, newSession) => { ipcRenderer.on('open-uri-requested', (event, url, newSession) => {
function handleError() { function handleError() {

View file

@ -132,7 +132,10 @@ export function doSetDaemonSetting(key, value, doNotDispatch = false) {
export function doCleanBlobs() { export function doCleanBlobs() {
return (dispatch) => { return (dispatch) => {
Lbry.blob_clean().then(dispatch(doFetchDaemonSettings())); Lbry.blob_clean().then(() => {
dispatch(doFetchDaemonSettings());
return 'done';
});
}; };
} }

View file

@ -45,6 +45,7 @@ export type AppState = {
interestedInYoutubeSync: boolean, interestedInYoutubeSync: boolean,
activeChannel: ?string, activeChannel: ?string,
incognito: boolean, incognito: boolean,
diskSpace: ?DiskSpace,
}; };
const defaultState: AppState = { const defaultState: AppState = {
@ -83,6 +84,7 @@ const defaultState: AppState = {
interestedInYoutubeSync: false, interestedInYoutubeSync: false,
activeChannel: undefined, activeChannel: undefined,
incognito: false, incognito: false,
diskSpace: null,
}; };
// @@router comes from react-router // @@router comes from react-router
@ -317,6 +319,13 @@ reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
}; };
}; };
reducers[ACTIONS.DISK_SPACE] = (state, action) => {
return {
...state,
diskSpace: action.data,
};
};
reducers[ACTIONS.SYNC_STATE_POPULATE] = (state, action) => { reducers[ACTIONS.SYNC_STATE_POPULATE] = (state, action) => {
const { welcomeVersion, allowAnalytics } = action.data; const { welcomeVersion, allowAnalytics } = action.data;
return { return {

View file

@ -139,3 +139,5 @@ export const selectActiveChannelStakedLevel = createSelector(
); );
export const selectIncognito = createSelector(selectState, (state) => state.incognito); export const selectIncognito = createSelector(selectState, (state) => state.incognito);
export const selectDiskSpace = createSelector(selectState, (state) => state.diskSpace);

View file

@ -15,6 +15,7 @@ export const diskSpaceLinux = (path) => {
}); });
}; };
// to implement
// export diskSpaceWindows = (path) => { // export diskSpaceWindows = (path) => {
// new Promise((resolve, reject) => { // new Promise((resolve, reject) => {
// //

View file

@ -3,3 +3,8 @@
export function formatNumberWithCommas(num: number, numberOfDigits?: number): string { export function formatNumberWithCommas(num: number, numberOfDigits?: number): string {
return num.toLocaleString('en', { minimumFractionDigits: numberOfDigits !== undefined ? numberOfDigits : 8 }); return num.toLocaleString('en', { minimumFractionDigits: numberOfDigits !== undefined ? numberOfDigits : 8 });
} }
export function isTrulyANumber(num: number) {
// typeof NaN = 'number' but NaN !== NaN
return typeof num === 'number' && num === num; // eslint-disable-line
}