From 4291c36c5867622ecddeb48850712fd87633a823 Mon Sep 17 00:00:00 2001 From: Thomas Zarebczan Date: Thu, 2 Jul 2020 18:42:04 -0400 Subject: [PATCH] Initial commit for LBRY-First fix killing daemon bump lbry-first --- .gitignore | 4 + build/downloadLBRYFirst.js | 91 +++++++++++++++++++ electron-builder.json | 5 + electron/LbryFirstInstance.js | 59 ++++++++++++ electron/index.js | 44 ++++++++- flow-typed/user.js | 1 + package.json | 12 ++- static/app-strings.json | 31 +------ .../publishAdditionalOptions/index.js | 10 +- .../publishAdditionalOptions/view.jsx | 90 +++++++++++++++++- ui/component/publishForm/view.jsx | 7 +- web/package.json | 2 +- web/yarn.lock | 4 +- webpack.electron.config.js | 6 +- yarn.lock | 8 +- 15 files changed, 323 insertions(+), 51 deletions(-) create mode 100644 build/downloadLBRYFirst.js create mode 100644 electron/LbryFirstInstance.js diff --git a/.gitignore b/.gitignore index be7bbfa8c..ca3fba407 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ package-lock.json .transifexrc .idea/ /build/daemon* +/lbrytv/dist/ +/lbrytv/node_modules +/static/lbry-first/ +/build/lbryFirst* /web/dist/* /web/node_modules/* /web/.env diff --git a/build/downloadLBRYFirst.js b/build/downloadLBRYFirst.js new file mode 100644 index 000000000..2c36dd736 --- /dev/null +++ b/build/downloadLBRYFirst.js @@ -0,0 +1,91 @@ +const path = require('path'); +const fs = require('fs'); +const packageJSON = require('../package.json'); +const fetch = require('node-fetch'); +const decompress = require('decompress'); +const os = require('os'); +const del = require('del'); + +const downloadLBRYFirst = targetPlatform => + new Promise((resolve, reject) => { + const lbryFirstURLTemplate = packageJSON.lbrySettings.LBRYFirstUrlTemplate; + const lbryFirstVersion = packageJSON.lbrySettings.LBRYFirstVersion; + const lbryFirstDir = path.join(__dirname, '..', packageJSON.lbrySettings.LBRYFirstDir); + let lbryFirstFileName = packageJSON.lbrySettings.LBRYFirstFileName; + + const currentPlatform = os.platform(); + + let lbryFirstPlatform = process.env.TARGET || targetPlatform || currentPlatform; + if (lbryFirstPlatform === 'linux') lbryFirstPlatform = 'Linux'; + if (lbryFirstPlatform === 'mac' || lbryFirstPlatform === 'darwin') lbryFirstPlatform = 'Darwin'; + if (lbryFirstPlatform === 'win32' || lbryFirstPlatform === 'windows') { + lbryFirstPlatform = 'Windows'; + lbryFirstFileName += '.exe'; + } + const lbryFirstFilePath = path.join(lbryFirstDir, lbryFirstFileName); + const lbryFirstVersionPath = path.join(__dirname, 'lbryFirst.ver'); + const tmpZipPath = path.join(__dirname, '..', 'dist', 'lbryFirst.zip'); + const lbryFirstURL = lbryFirstURLTemplate.replace(/LBRYFIRSTVER/g, lbryFirstVersion).replace(/OSNAME/g, lbryFirstPlatform); + console.log('URL:', lbryFirstURL); + + // If a lbryFirst and lbryFirst.ver exists, check to see if it matches the current lbryFirst version + const hasLbryFirstDownloaded = fs.existsSync(lbryFirstFilePath); + const hasLbryFirstVersion = fs.existsSync(lbryFirstVersionPath); + let downloadedLbryFirstVersion; + + if (hasLbryFirstVersion) { + downloadedLbryFirstVersion = fs.readFileSync(lbryFirstVersionPath, 'utf8'); + } + + if (hasLbryFirstDownloaded && hasLbryFirstVersion && downloadedLbryFirstVersion === lbryFirstVersion) { + console.log('\x1b[34minfo\x1b[0m LbryFirst already downloaded'); + resolve('Done'); + } else { + console.log('\x1b[34minfo\x1b[0m Downloading lbryFirst...'); + fetch(lbryFirstURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/zip', + }, + }) + .then(response => response.buffer()) + .then( + result => + new Promise((newResolve, newReject) => { + const distPath = path.join(__dirname, '..', 'dist'); + const hasDistFolder = fs.existsSync(distPath); + + if (!hasDistFolder) { + fs.mkdirSync(distPath); + } + + fs.writeFile(tmpZipPath, result, error => { + if (error) return newReject(error); + return newResolve(); + }); + }) + ) + .then(() => del(`${lbryFirstFilePath}*`)) + .then() + .then(() => + decompress(tmpZipPath, lbryFirstDir, { + filter: file => path.basename(file.path) === lbryFirstFileName, + }) + ) + .then(() => { + console.log('\x1b[32msuccess\x1b[0m LbryFirst downloaded!'); + if (hasLbryFirstVersion) { + del(lbryFirstVersionPath); + } + + fs.writeFileSync(lbryFirstVersionPath, lbryFirstVersion, 'utf8'); + resolve('Done'); + }) + .catch(error => { + console.error(`\x1b[31merror\x1b[0m LbryFirst download failed due to: \x1b[35m${error}\x1b[0m`); + reject(error); + }); + } + }); + +downloadLBRYFirst(); diff --git a/electron-builder.json b/electron-builder.json index bd6760582..7e6fba113 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -20,6 +20,11 @@ "to": "static/daemon/", "filter": ["**/*"] }, + { + "from": "./static/lbry-first/", + "to": "static/lbry-first/", + "filter": ["**/*"] + }, { "from": "./static/img", "to": "static/img", diff --git a/electron/LbryFirstInstance.js b/electron/LbryFirstInstance.js new file mode 100644 index 000000000..191d81ef4 --- /dev/null +++ b/electron/LbryFirstInstance.js @@ -0,0 +1,59 @@ +import path from 'path'; +import { spawn, execSync } from 'child_process'; + +export default class LbryFirstInstance { + static lbryFirstPath = + process.env.LBRY_FIRST_DAEMON || + (process.env.NODE_ENV === 'production' + ? path.join(process.resourcesPath, 'static/lbry-first', 'lbry-first') + : path.join(__static, 'lbry-first/lbry-first')); + + static headersPath = + process.env.LBRY_FIRST_DAEMON || + (process.env.NODE_ENV === 'production' + ? path.join(process.resourcesPath, 'static/lbry-first', 'headers') + : path.join(__static, 'lbry-first/headers')); + + subprocess; + handlers; + + constructor() { + this.handlers = []; + } + + launch() { + let flags = ['serve']; + console.log(`LbryFirst: ${LbryFirstInstance.lbryFirstPath}`); + this.subprocess = spawn(LbryFirstInstance.lbryFirstPath, flags); + this.subprocess.stdout.on('data', data => console.log(`LbryFirst: ${data}`)); + this.subprocess.stderr.on('data', data => console.error(`LbryFirst: ${data}`)); + this.subprocess.on('exit', () => this.fire('exit')); + this.subprocess.on('error', error => console.error(`LbryFirst error: ${error}`)); + } + + quit() { + if (process.platform === 'win32') { + try { + execSync(`taskkill /pid ${this.subprocess.pid} /t /f`); + } catch (error) { + console.error(error.message); + } + } else { + this.subprocess.kill(); + } + } + + // Follows the publish/subscribe pattern + + // Subscribe method + on(event, handler, context = handler) { + this.handlers.push({ event, handler: handler.bind(context) }); + } + + // Publish method + fire(event, args) { + this.handlers.forEach(topic => { + if (topic.event === event) topic.handler(args); + }); + } +} diff --git a/electron/index.js b/electron/index.js index 2af54625a..5601c9c84 100644 --- a/electron/index.js +++ b/electron/index.js @@ -6,7 +6,8 @@ import SemVer from 'semver'; import https from 'https'; import { app, dialog, ipcMain, session, shell } from 'electron'; import { autoUpdater } from 'electron-updater'; -import { Lbry } from 'lbry-redux'; +import { Lbry, LbryFirst } from 'lbry-redux'; +import LbryFirstInstance from './LbryFirstInstance'; import Daemon from './Daemon'; import isDev from 'electron-is-dev'; import createTray from './createTray'; @@ -41,6 +42,7 @@ let rendererWindow; let tray; // eslint-disable-line let daemon; +let lbryFirst; const appState = {}; @@ -87,6 +89,41 @@ const startDaemon = async () => { } }; +const startLbryFirst = async () => { + let isLbryFirstRunning = false; + console.log(`LbryFirst: Start LBRY First App`); + try { + await LbryFirst.status(); + isLbryFirstRunning = true; + } catch (e) { + console.log('status fails', e); + console.log('Starting LbryFirst'); + } + + if (!isLbryFirstRunning) { + console.log('start process...'); + lbryFirst = new LbryFirstInstance(); + lbryFirst.on('exit', () => { + if (!isDev) { + lbryFirst = null; + if (!appState.isQuitting) { + dialog.showErrorBox( + 'LbryFirst has Exited', + 'The lbryFirst may have encountered an unexpected error, or another lbryFirst instance is already running. \n\n' + ); + } + app.quit(); + } + }); + + try { + await lbryFirst.launch(); + } catch (e) { + console.log('e', e); + } + } +}; + // When we are starting the app, ensure there are no other apps already running const gotSingleInstanceLock = app.requestSingleInstanceLock(); @@ -123,6 +160,7 @@ if (!gotSingleInstanceLock) { app.on('ready', async () => { await startDaemon(); + await startLbryFirst(); startSandbox(); if (isDev) { @@ -205,6 +243,10 @@ app.on('will-quit', event => { daemon.quit(); event.preventDefault(); } + if (lbryFirst) { + lbryFirst.quit(); + event.preventDefault(); + } if (rendererWindow) { rendererWindow = null; diff --git a/flow-typed/user.js b/flow-typed/user.js index 5a06ff60e..bfb3142a3 100644 --- a/flow-typed/user.js +++ b/flow-typed/user.js @@ -26,4 +26,5 @@ declare type User = { updated_at: string, youtube_channels: ?Array, device_types: Array, + lbry_first_approved: boolean, }; diff --git a/package.json b/package.json index 5649159f1..9b7525a57 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "precommit": "lint-staged", "preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc", "postinstall": "cd web && yarn && cd .. && if-env NODE_ENV=production && yarn postinstall:warning || if-env APP_ENV=web && echo 'Done installing deps' || yarn postinstall:electron", - "postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js", + "postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js && node ./build/downloadLBRYFirst.js", "postinstall:warning": "echo '\n\nWARNING\n\nNot all node modules were installed because NODE_ENV is set to \"production\".\nThis should only be set after installing dependencies with \"yarn\". The app will not work.\n\n'" }, "dependencies": { @@ -136,8 +136,8 @@ "imagesloaded": "^4.1.4", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#906199d866a187015668a27363f010828c15979a", - "lbryinc": "lbryio/lbryinc#72eee35f5181940eb4a468a27ddb2a2a4e362fb0", + "lbry-redux": "lbryio/lbry-redux#67d8540ccc0fbedf018ae0bdfe3cae17660a5a6c", + "lbryinc": "lbryio/lbryinc#0f6fd2c33812fb1c1e393dd35b1a7560d3ba3fe2", "lint-staged": "^7.0.2", "localforage": "^1.7.1", "lodash-es": "^4.17.14", @@ -217,6 +217,10 @@ "lbrynetDaemonVersion": "0.77.0", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonDir": "static/daemon", - "lbrynetDaemonFileName": "lbrynet" + "lbrynetDaemonFileName": "lbrynet", + "LBRYFirstVersion": "0.0.19", + "LBRYFirstUrlTemplate": "https://github.com/lbryio/lbry-first/releases/download/vLBRYFIRSTVER/lbry-first_OSNAME_amd64.zip", + "LBRYFirstDir": "static/lbry-first", + "LBRYFirstFileName": "lbry-first" } } diff --git a/static/app-strings.json b/static/app-strings.json index dfbfa039d..36c5fd97b 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -1261,34 +1261,5 @@ "Delete Your Channel": "Delete Your Channel", "Remove from Blocked List": "Remove from Blocked List", "Are you sure you want to remove this from the list?": "Are you sure you want to remove this from the list?", - "Cover": "Cover", - "A name is required for your url": "A name is required for your url", - "Create Channel": "Create Channel", - "CableTube Escape Artists": "CableTube Escape Artists", - "Advanced Filters from URL": "Advanced Filters from URL", - "General": "General", - "MyAwesomeChannel": "MyAwesomeChannel", - "My Awesome Channel": "My Awesome Channel", - "Increasing your deposit can help your channel be discovered more easily.": "Increasing your deposit can help your channel be discovered more easily.", - "Editing @%channel%": "Editing @%channel%", - "This field cannot be changed.": "This field cannot be changed.", - "Delete Channel": "Delete Channel", - "Edit Thumbnail Image": "Edit Thumbnail Image", - "Choose Image": "Choose Image", - "File to upload": "File to upload", - "Uploading...": "Uploading...", - "Use a URL": "Use a URL", - "Edit Cover Image": "Edit Cover Image", - "Cover Image": "Cover Image", - "(6.25:1)": "(6.25:1)", - "(1:1)": "(1:1)", - "You Followed Your First Channel!": "You Followed Your First Channel!", - "Awesome! You just followed your first first channel.": "Awesome! You just followed your first first channel.", - "After submitting, it will take a few minutes for your changes to be live for everyone.": "After submitting, it will take a few minutes for your changes to be live for everyone.", - "Anything": "Anything", - "Paid": "Paid", - "Start at": "Start at", - "Links": "Links", - "LBRY URL": "LBRY URL", - "Download Link": "Download Link" + "Automagically upload to your youtube channel!": "Automagically upload to your youtube channel!" } diff --git a/ui/component/publishAdditionalOptions/index.js b/ui/component/publishAdditionalOptions/index.js index fa963ea73..98ec5272f 100644 --- a/ui/component/publishAdditionalOptions/index.js +++ b/ui/component/publishAdditionalOptions/index.js @@ -1,16 +1,18 @@ import { connect } from 'react-redux'; import { selectPublishFormValues, doUpdatePublishForm } from 'lbry-redux'; import PublishPage from './view'; +import { selectUser, selectAccessToken } from '../../redux/selectors/user'; +import {doFetchAccessToken} from '../../redux/actions/user'; const select = state => ({ ...selectPublishFormValues(state), + accessToken: selectAccessToken(state), + user: selectUser(state), }); const perform = dispatch => ({ updatePublishForm: value => dispatch(doUpdatePublishForm(value)), + fetchAccessToken: () => dispatch(doFetchAccessToken()), }); -export default connect( - select, - perform -)(PublishPage); +export default connect(select, perform)(PublishPage); diff --git a/ui/component/publishAdditionalOptions/view.jsx b/ui/component/publishAdditionalOptions/view.jsx index 3d505cf5b..e1a981632 100644 --- a/ui/component/publishAdditionalOptions/view.jsx +++ b/ui/component/publishAdditionalOptions/view.jsx @@ -4,10 +4,13 @@ import classnames from 'classnames'; import usePersistedState from 'effects/use-persisted-state'; import { FormField } from 'component/common/form'; import Button from 'component/button'; +import { LbryFirst } from 'lbry-redux'; + import LicenseType from './license-type'; import Card from 'component/common/card'; type Props = { + user: ?User, language: ?string, name: ?string, licenseType: ?string, @@ -15,15 +18,73 @@ type Props = { licenseUrl: ?string, disabled: boolean, updatePublishForm: ({}) => void, + useLBRYUploader: boolean, + needsYTAuth: boolean, + fetchAccessToken: () => void, + accessToken: string, }; -function PublishAdvanced(props: Props) { - const { language, name, licenseType, otherLicenseDescription, licenseUrl, updatePublishForm } = props; +function PublishAdditionalOptions(props: Props) { + const { + user, + language, + name, + licenseType, + otherLicenseDescription, + licenseUrl, + updatePublishForm, + useLBRYUploader, + needsYTAuth, + fetchAccessToken, + accessToken, + } = props; const [hideSection, setHideSection] = usePersistedState('publish-advanced-options', true); + const isLBRYFirstUser = user && user.lbry_first_approved; function toggleHideSection() { setHideSection(!hideSection); } + function signup() { + updatePublishForm({ ytSignupPending: true }); + LbryFirst.ytSignup() + .then(response => { + console.log(response); + updatePublishForm({ needsYTAuth: false, ytSignupPending: false }); + }) + .catch(error => { + updatePublishForm({ ytSignupPending: false }); + console.log(error); + }); + } + function unlink() { + LbryFirst.remove() + .then(response => { + console.log(response); + updatePublishForm({ needsYTAuth: true }); + }) + .catch(error => { + console.log(error); + }); + } + + React.useEffect(() => { + if (!accessToken) { + fetchAccessToken(); + } + }, [accessToken, fetchAccessToken]); + + React.useEffect(() => { + if (useLBRYUploader) { + LbryFirst.hasYTAuth(accessToken) + .then(response => { + console.log(response); + updatePublishForm({ needsYTAuth: !response.HasAuth }); + }) + .catch(error => { + console.log(error); + }); + } + }, [accessToken, updatePublishForm, useLBRYUploader]); return ( {!hideSection && (
+ {!IS_WEB && isLBRYFirstUser && ( + updatePublishForm({ useLBRYUploader: !useLBRYUploader })} + label={ + + {__('Automagically upload to your youtube channel!')}{' '} +
+ )} + {!IS_WEB && useLBRYUploader && needsYTAuth && isLBRYFirstUser && ( +
+
+ )} void, checkAvailability: string => void, onChannelChange: string => void, + ytSignupPending: boolean, }; function PublishForm(props: Props) { @@ -92,6 +94,7 @@ function PublishForm(props: Props) { disabled = false, checkAvailability, onChannelChange, + ytSignupPending, } = props; const TAGS_LIMIT = 5; const formDisabled = (!filePath && !editingURI) || publishing; @@ -217,7 +220,9 @@ function PublishForm(props: Props) { button="primary" onClick={() => publish(filePath)} label={submitLabel} - disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS} + disabled={ + formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS || ytSignupPending + } />