Prechádzať zdrojové kódy

Rewrote frontend download download function to make use of forked processes. This means that the download runs in full async (essentially in a separate thread). Updated the discord wrapper to be more dynamic. Updated auth manager to use async/await.

Daniel Scalzi 7 rokov pred
rodič
commit
d4d7be7c47

+ 181 - 93
app/assets/js/actionbinder.js

@@ -1,3 +1,4 @@
+const cp = require('child_process')
 const path = require('path')
 const {AssetGuard} = require(path.join(__dirname, 'assets', 'js', 'assetguard.js'))
 const ProcessBuilder = require(path.join(__dirname, 'assets', 'js', 'processbuilder.js'))
@@ -8,14 +9,25 @@ const AuthManager = require(path.join(__dirname, 'assets', 'js', 'authmanager.js
 
 let mojangStatusListener
 
+// Launch Elements
+let launch_content, launch_details, launch_progress, launch_progress_label, launch_details_text
+
 // Synchronous Listener
 document.addEventListener('readystatechange', function(){
     if (document.readyState === 'interactive'){
 
+        // Save a reference to the launch elements.
+        launch_content = document.getElementById('launch_content')
+        launch_details = document.getElementById('launch_details')
+        launch_progress = document.getElementById('launch_progress')
+        launch_progress_label = document.getElementById('launch_progress_label')
+        launch_details_text = document.getElementById('launch_details_text')
+
         // Bind launch button
         document.getElementById('launch_button').addEventListener('click', function(e){
             console.log('Launching game..')
-            testdownloads()
+            //testdownloads()
+            dlAsync()
         })
 
         // TODO convert this to dropdown menu.
@@ -58,104 +70,180 @@ document.addEventListener('readystatechange', function(){
     }
 }, false)
 
-// Keep reference to AssetGuard object temporarily
-let tracker;
+// Keep reference to Minecraft Process
+let proc
+// Is DiscordRPC enabled
+let hasRPC = false
+// Joined server regex
+const servJoined = /[[0-2][0-9]:[0-6][0-9]:[0-6][0-9]\] \[Client thread\/INFO\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/g
+const gameJoined = /\[[0-2][0-9]:[0-6][0-9]:[0-6][0-9]\] \[Client thread\/WARN\]: Skipping bad option: lastServer:/g
+const gameJoined2 = /\[[0-2][0-9]:[0-6][0-9]:[0-6][0-9]\] \[Client thread\/INFO\]: Created: \d+x\d+ textures-atlas/g
+
+let aEx
+let currentProc
+let serv
+let versionData
+let forgeData
+
+function dlAsync(login = true){
+
+    // Login parameter is temporary for debug purposes. Allows testing the validation/downloads without
+    // launching the game.
+
+    if(login) {
+        if(ConfigManager.getSelectedAccount() == null){
+            console.error('login first.')
+            //in devtools AuthManager.addAccount(username, pass)
+            return
+        }
+    }
+
+    launch_details_text.innerHTML = 'Please wait..'
+    launch_progress.setAttribute('max', '100')
+    launch_details.style.display = 'flex'
+    launch_content.style.display = 'none'
 
-testdownloads = async function(){
+    aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [
+        ConfigManager.getGameDirectory(),
+        ConfigManager.getJavaExecutable()
+    ])
 
-    if(ConfigManager.getSelectedAccount() == null){
-        console.error('login first.')
-        //in devtools AuthManager.addAccount(username, pass)
-        return
-    }
+    aEx.on('message', (m) => {
+        if(currentProc === 'validateDistribution'){
 
-    const content = document.getElementById('launch_content')
-    const details = document.getElementById('launch_details')
-    const progress = document.getElementById('launch_progress')
-    const progress_text = document.getElementById('launch_progress_label')
-    const det_text = document.getElementById('launch_details_text')
-
-    det_text.innerHTML = 'Please wait..'
-    progress.setAttribute('max', '100')
-    details.style.display = 'flex'
-    content.style.display = 'none'
-
-    tracker = new AssetGuard(ConfigManager.getGameDirectory(), ConfigManager.getJavaExecutable())
-
-    det_text.innerHTML = 'Loading server information..'
-    const serv = await tracker.validateDistribution(ConfigManager.getSelectedServer())
-    progress.setAttribute('value', 20)
-    progress_text.innerHTML = '20%'
-    console.log('forge stuff done')
-
-    det_text.innerHTML = 'Loading version information..'
-    const versionData = await tracker.loadVersionData(serv.mc_version)
-    progress.setAttribute('value', 40)
-    progress_text.innerHTML = '40%'
-
-    det_text.innerHTML = 'Validating asset integrity..'
-    await tracker.validateAssets(versionData)
-    progress.setAttribute('value', 60)
-    progress_text.innerHTML = '60%'
-    console.log('assets done')
-
-    det_text.innerHTML = 'Validating library integrity..'
-    await tracker.validateLibraries(versionData)
-    progress.setAttribute('value', 80)
-    progress_text.innerHTML = '80%'
-    console.log('libs done')
-
-    det_text.innerHTML = 'Validating miscellaneous file integrity..'
-    await tracker.validateMiscellaneous(versionData)
-    progress.setAttribute('value', 100)
-    progress_text.innerHTML = '100%'
-    console.log('files done')
-
-    det_text.innerHTML = 'Downloading files..'
-    tracker.on('totaldlprogress', function(data){
-        progress.setAttribute('max', data.total)
-        progress.setAttribute('value', data.acc)
-        progress_text.innerHTML = parseInt((data.acc/data.total)*100) + '%'
-    })
+            launch_progress.setAttribute('value', 20)
+            launch_progress_label.innerHTML = '20%'
+            serv = m.result
+            console.log('forge stuff done')
 
-    tracker.on('dlcomplete', async function(){
-
-        det_text.innerHTML = 'Preparing to launch..'
-        const forgeData = await tracker.loadForgeData(serv.id)
-        const authUser = await AuthManager.validateSelected()
-        let pb = new ProcessBuilder(ConfigManager.getGameDirectory(), serv, versionData, forgeData, authUser)
-        det_text.innerHTML = 'Launching game..'
-        let proc;
-        try{
-            proc = pb.build()
-            det_text.innerHTML = 'Done. Enjoy the server!'
-            const tempListener = function(data){
-                if(data.indexOf('[Client thread/INFO]: -- System Details --') > -1){
-                    details.style.display = 'none'
-                    content.style.display = 'inline-flex'
-                    proc.stdout.removeListener('data', tempListener)
-                }
+            // Begin version load.
+            launch_details_text.innerHTML = 'Loading version information..'
+            currentProc = 'loadVersionData'
+            aEx.send({task: 0, content: currentProc, argsArr: [serv.mc_version]})
+
+        } else if(currentProc === 'loadVersionData'){
+
+            launch_progress.setAttribute('value', 40)
+            launch_progress_label.innerHTML = '40%'
+            versionData = m.result
+
+            // Begin asset validation.
+            launch_details_text.innerHTML = 'Validating asset integrity..'
+            currentProc = 'validateAssets'
+            aEx.send({task: 0, content: currentProc, argsArr: [versionData]})
+
+        } else if(currentProc === 'validateAssets'){
+
+            launch_progress.setAttribute('value', 60)
+            launch_progress_label.innerHTML = '60%'
+            console.log('assets done')
+
+            // Begin library validation.
+            launch_details_text.innerHTML = 'Validating library integrity..'
+            currentProc = 'validateLibraries'
+            aEx.send({task: 0, content: currentProc, argsArr: [versionData]})
+
+        } else if(currentProc === 'validateLibraries'){
+
+            launch_progress.setAttribute('value', 80)
+            launch_progress_label.innerHTML = '80%'
+            console.log('libs done')
+
+            // Begin miscellaneous validation.
+            launch_details_text.innerHTML = 'Validating miscellaneous file integrity..'
+            currentProc = 'validateMiscellaneous'
+            aEx.send({task: 0, content: currentProc, argsArr: [versionData]})
+
+        } else if(currentProc === 'validateMiscellaneous'){
+
+            launch_progress.setAttribute('value', 100)
+            launch_progress_label.innerHTML = '100%'
+            console.log('files done')
+
+            launch_details_text.innerHTML = 'Downloading files..'
+            currentProc = 'processDlQueues'
+            aEx.send({task: 0, content: currentProc})
+
+        } else if(currentProc === 'processDlQueues'){
+            if(m.task === 0){
+                remote.getCurrentWindow().setProgressBar(m.value/m.total)
+                launch_progress.setAttribute('max', m.total)
+                launch_progress.setAttribute('value', m.value)
+                launch_progress_label.innerHTML = m.percent + '%'
+            } else if(m.task === 1){
+                remote.getCurrentWindow().setProgressBar(-1)
+
+                launch_details_text.innerHTML = 'Preparing to launch..'
+                currentProc = 'loadForgeData'
+                aEx.send({task: 0, content: currentProc, argsArr: [serv.id]})
+
+            } else {
+                console.error('Unknown download data type.', m)
             }
-            proc.stdout.on('data', tempListener)
-            // Init Discord Hook (Untested)
-            const distro = AssetGuard.retrieveDistributionDataSync(ConfigManager.getGameDirectory)
-            if(distro.discord != null && serv.discord != null){
-                DiscordWrapper.initRPC(distro.discord, serv.discord)
-                proc.on('close', (code, signal) => {
-                    DiscordWrapper.shutdownRPC()
-                })
+        } else if(currentProc === 'loadForgeData'){
+
+            forgeData = m.result
+
+            if(login) {
+                //if(!(await AuthManager.validateSelected())){
+                    // 
+                //}
+                const authUser = ConfigManager.getSelectedAccount();
+                console.log('authu', authUser)
+                let pb = new ProcessBuilder(ConfigManager.getGameDirectory(), serv, versionData, forgeData, authUser)
+                launch_details_text.innerHTML = 'Launching game..'
+                try{
+                    proc = pb.build()
+                    launch_details_text.innerHTML = 'Done. Enjoy the server!'
+                    const tempListener = function(data){
+                        if(data.indexOf('[Client thread/INFO]: -- System Details --') > -1){
+                            launch_details.style.display = 'none'
+                            launch_content.style.display = 'inline-flex'
+                            if(hasRPC){
+                                DiscordWrapper.updateDetails('Loading game..')
+                            }
+                            proc.stdout.removeListener('data', tempListener)
+                        }
+                    }
+                    const gameStateChange = function(data){
+                        if(servJoined.test(data)){
+                            DiscordWrapper.updateDetails('Exploring the Realm!')
+                        } else if(gameJoined.test(data)){
+                            DiscordWrapper.updateDetails('Idling on Main Menu')
+                        }
+                    }
+                    proc.stdout.on('data', tempListener)
+                    proc.stdout.on('data', gameStateChange)
+                    // Init Discord Hook (Untested)
+                    const distro = AssetGuard.retrieveDistributionDataSync(ConfigManager.getGameDirectory)
+                    if(distro.discord != null && serv.discord != null){
+                        DiscordWrapper.initRPC(distro.discord, serv.discord)
+                        hasRPC = true
+                        proc.on('close', (code, signal) => {
+                            console.log('Shutting down Discord Rich Presence..')
+                            DiscordWrapper.shutdownRPC()
+                            hasRPC = false
+                            proc = null
+                        })
+                    }
+                } catch(err) {
+                    //launch_details_text.innerHTML = 'Error: ' + err.message;
+                    launch_details_text.innerHTML = 'Error: See log for details..';
+                    console.log(err)
+                    setTimeout(function(){
+                        launch_details.style.display = 'none'
+                        launch_content.style.display = 'inline-flex'
+                    }, 5000)
+                }
             }
-        } catch(err) {
-            //det_text.innerHTML = 'Error: ' + err.message;
-            det_text.innerHTML = 'Error: See log for details..';
-            console.log(err)
-            setTimeout(function(){
-                details.style.display = 'none'
-                content.style.display = 'inline-flex'
-            }, 5000)
+
+            // Disconnect from AssetExec
+            aEx.disconnect()
+
         }
-        // Remove reference to tracker.
-        tracker = null
     })
-    tracker.processDlQueues()
+
+    launch_details_text.innerHTML = 'Loading server information..'
+    currentProc = 'validateDistribution'
+    aEx.send({task: 0, content: currentProc, argsArr: [ConfigManager.getSelectedServer()]})
 }

+ 37 - 0
app/assets/js/assetexec.js

@@ -0,0 +1,37 @@
+const {AssetGuard} = require('./assetguard.js')
+
+const tracker = new AssetGuard(process.argv[2], process.argv[3])
+console.log('AssetExec Started')
+
+// Temporary for debug purposes.
+process.on('unhandledRejection', r => console.log(r))
+
+tracker.on('totaldlprogress', (data) => {
+    process.send({task: 0, total: data.total, value: data.acc, percent: parseInt((data.acc/data.total)*100)})
+})
+
+tracker.on('dlcomplete', () => {
+    process.send({task: 1})
+})
+
+process.on('message', (msg) => {
+    if(msg.task === 0){
+        const func = msg.content
+        if(typeof tracker[func] === 'function'){
+            const f = tracker[func]
+            const res = f.apply(tracker, msg.argsArr)
+            if(res instanceof Promise){
+                res.then((v) => {
+                    process.send({result: v})
+                })
+            } else {
+                process.send({result: res})
+            }
+        }
+    }
+})
+
+process.on('disconnect', () => {
+    console.log('AssetExec Disconnected')
+    process.exit(0)
+})

+ 4 - 6
app/assets/js/assetguard.js

@@ -498,7 +498,6 @@ class AssetGuard extends EventEmitter {
      */
     startAsyncProcess(identifier, limit = 5){
         const self = this
-        let win = remote.getCurrentWindow()
         let acc = 0
         const concurrentDlTracker = this[identifier]
         const concurrentDlQueue = concurrentDlTracker.dlqueue.slice(0)
@@ -526,7 +525,6 @@ class AssetGuard extends EventEmitter {
                         req.abort()
                         console.log('Failed to download ' + asset.from + '. Response code', resp.statusCode)
                         self.progress += asset.size*1
-                        win.setProgressBar(self.progress/self.totaldlsize)
                         self.emit('totaldlprogress', {acc: self.progress, total: self.totaldlsize})
                         cb()
                     }
@@ -536,8 +534,6 @@ class AssetGuard extends EventEmitter {
                     self.progress += chunk.length
                     acc += chunk.length
                     self.emit(identifier + 'dlprogress', acc)
-                    //console.log(identifier + ' Progress', acc/this[identifier].dlsize)
-                    win.setProgressBar(self.progress/self.totaldlsize)
                     self.emit('totaldlprogress', {acc: self.progress, total: self.totaldlsize})
                 })
             }, function(err){
@@ -552,7 +548,6 @@ class AssetGuard extends EventEmitter {
                 self.progress -= self[identifier].dlsize
                 self[identifier] = new DLTracker([], 0)
                 if(self.totaldlsize === 0) {
-                    win.setProgressBar(-1)
                     self.emit('dlcomplete')
                 }
             })
@@ -811,6 +806,10 @@ class AssetGuard extends EventEmitter {
                 }*/
                 const serv = AssetGuard.getServerById(self.basePath, serverpackid)
 
+                if(serv == null) {
+                    console.error('Invalid server pack id:', serverpackid)
+                }
+
                 self.forge = self._parseDistroModules(serv.modules, serv.mc_version)
                 //Correct our workaround here.
                 let decompressqueue = self.forge.callback
@@ -944,7 +943,6 @@ class AssetGuard extends EventEmitter {
      */
     processDlQueues(identifiers = [{id:'assets', limit:20}, {id:'libraries', limit:5}, {id:'files', limit:5}, {id:'forge', limit:5}]){
         this.progress = 0;
-        let win = remote.getCurrentWindow()
 
         let shouldFire = true
 

+ 22 - 17
app/assets/js/authmanager.js

@@ -1,25 +1,30 @@
 const ConfigManager = require('./configmanager.js')
 const Mojang = require('./mojang.js')
 
-exports.addAccount = function(username, password){
-    return new Promise(async function(resolve, reject){
-        const session = await Mojang.authenticate(username, password, ConfigManager.getClientToken)
-        const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
-        ConfigManager.save()
-        resolve(ret)
-    })
+exports.addAccount = async function(username, password){
+    const session = await Mojang.authenticate(username, password, ConfigManager.getClientToken)
+    const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
+    ConfigManager.save()
+    return ret
 }
 
-exports.validateSelected = function(){
-    return new Promise(async function(resolve, reject){
-        const current = ConfigManager.getSelectedAccount()
-        if(!await Mojang.validate(current.accessToken, ConfigManager.getClientToken)){
-            const session = Mojang.refresh(current.accessToken, ConfigManager.getClientToken)
-            const ret = ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
+exports.validateSelected = async function(){
+    const current = ConfigManager.getSelectedAccount()
+    const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
+    console.log(isValid)
+    if(!isValid){
+        try {
+            const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
+            console.log('ses', session)
+            ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
             ConfigManager.save()
-            resolve(ret)
-        } else {
-            resolve(current)
+        } catch(err) {
+            if(err && err.message === 'ForbiddenOperationException'){
+                return false
+            }
         }
-    })
+        return true
+    } else {
+        return true
+    }
 }

+ 15 - 6
app/assets/js/discordwrapper.js

@@ -3,16 +3,15 @@ const {Client} = require('discord-rpc')
 const ConfigManager = require('./configmanager.js')
 
 let rpc
+let activity
 
-exports.initRPC = function(genSettings, servSettings){
-    rpc = new Client({ transport: 'ipc' });
+exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting for Client..'){
+    rpc = new Client({ transport: 'ipc' })
 
     rpc.on('ready', () => {
-        const activity = {
-            // state = top text
-            // details = bottom text
+        activity = {
+            details: initialDetails,
             state: 'Server: ' + servSettings.shortId,
-            details: 'Exploring the wall',
             largeImageKey: servSettings.largeImageKey,
             largeImageText: servSettings.largeImageText,
             smallImageKey: genSettings.smallImageKey,
@@ -33,8 +32,18 @@ exports.initRPC = function(genSettings, servSettings){
     })
 }
 
+exports.updateDetails = function(details){
+    if(activity == null){
+        console.error('Discord RPC is not initialized and therefore cannot be updated.')
+    }
+    activity.details = details
+    rpc.setActivity(activity)
+}
+
 exports.shutdownRPC = function(){
+    if(!rpc) return
     rpc.setActivity({})
     rpc.destroy()
     rpc = null
+    activity = null
 }