Browse Source

Progress towards final asset downloading system (assetguard).

Daniel Scalzi 8 năm trước cách đây
mục cha
commit
0dfb26f8fa

+ 7 - 11
app/assets/js/assetdownload.js

@@ -4,9 +4,13 @@ const path = require('path')
 const mkpath = require('mkdirp');
 const mkpath = require('mkdirp');
 const async = require('async')
 const async = require('async')
 const crypto = require('crypto')
 const crypto = require('crypto')
-const library = require('./library.js')
+const Library = require('./library.js')
 const {BrowserWindow} = require('electron')
 const {BrowserWindow} = require('electron')
 
 
+/**
+ * PHASING THIS OUT, WILL BE REMOVED WHEN ASSET GUARD MODULE IS COMPLETE!
+ */
+
 function Asset(from, to, size, hash){
 function Asset(from, to, size, hash){
     this.from = from
     this.from = from
     this.to = to
     this.to = to
@@ -22,14 +26,6 @@ function AssetIndex(id, sha1, size, url, totalSize){
     this.totalSize = totalSize
     this.totalSize = totalSize
 }
 }
 
 
-function Library(id, sha1, size, from, to){
-    this.id = id
-    this.sha1 = sha1
-    this.size = size
-    this.from = from
-    this.to = to
-}
-
 /**
 /**
  * This function will download the version index data and read it into a Javascript
  * This function will download the version index data and read it into a Javascript
  * Object. This object will then be returned.
  * Object. This object will then be returned.
@@ -109,12 +105,12 @@ downloadLibraries = function(versionData, basePath){
 
 
     //Check validity of each library. If the hashs don't match, download the library.
     //Check validity of each library. If the hashs don't match, download the library.
     libArr.forEach(function(lib, index){
     libArr.forEach(function(lib, index){
-        if(library.validateRules(lib.rules)){
+        if(Library.validateRules(lib.rules)){
             let artifact = null
             let artifact = null
             if(lib.natives == null){
             if(lib.natives == null){
                 artifact = lib.downloads.artifact
                 artifact = lib.downloads.artifact
             } else {
             } else {
-                artifact = lib.downloads.classifiers[lib.natives[library.mojangFriendlyOS()]]
+                artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()]]
             }
             }
             const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, path.join(libPath, artifact.path))
             const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, path.join(libPath, artifact.path))
             if(!validateLocalIntegrity(libItm.to, 'sha1', libItm.sha1)){
             if(!validateLocalIntegrity(libItm.to, 'sha1', libItm.sha1)){

+ 299 - 0
app/assets/js/assetguard.js

@@ -0,0 +1,299 @@
+/* Requirements */
+const fs = require('fs')
+const request = require('request')
+const path = require('path')
+const mkpath = require('mkdirp');
+const async = require('async')
+const crypto = require('crypto')
+const EventEmitter = require('events');
+const {remote} = require('electron')
+
+/* Classes */
+
+class Asset{
+    constructor(id, hash, size, from, to){
+        this.id = id
+        this.hash = hash
+        this.size = size
+        this.from = from
+        this.to = to
+    }
+}
+
+class Library extends Asset{
+
+    static mojangFriendlyOS(){
+        const opSys = process.platform
+        if (opSys === 'darwin') {
+            return 'osx';
+        } else if (opSys === 'win32'){
+            return 'windows';
+        } else if (opSys === 'linux'){
+            return 'linux';
+        } else {
+            return 'unknown_os';
+        }
+    }
+
+    static validateRules(rules){
+        if(rules == null) return true
+
+        let result = true
+        rules.forEach(function(rule){
+            const action = rule['action']
+            const osProp = rule['os']
+            if(action != null){
+                if(osProp != null){
+                    const osName = osProp['name']
+                    const osMoj = Library.mojangFriendlyOS()
+                    if(action === 'allow'){
+                        result = osName === osMoj
+                        return
+                    } else if(action === 'disallow'){
+                        result = osName !== osMoj
+                        return
+                    }
+                }
+            }
+        })
+        return result
+    }
+}
+
+class DLTracker {
+    constructor(dlqueue, dlsize){
+        this.dlqueue = dlqueue
+        this.dlsize = dlsize
+    }
+}
+
+class AssetGuard extends EventEmitter{
+    constructor(){
+        super()
+        this.totaldlsize = 0;
+        this.progress = 0;
+        this.assets = new DLTracker([], 0)
+        this.libraries = new DLTracker([], 0)
+        this.files = new DLTracker([], 0)
+    }
+}
+
+//Instance of AssetGuard
+
+const instance = new AssetGuard()
+
+/* Utility Functions */
+
+validateLocal = function(filePath, algo, hash){
+    if(fs.existsSync(filePath)){
+        let fileName = path.basename(filePath)
+        let shasum = crypto.createHash(algo)
+        let content = fs.readFileSync(filePath)
+        shasum.update(content)
+        let calcdhash = shasum.digest('hex')
+        return calcdhash === hash
+    }
+    return false;
+}
+
+/* Validation Functions */
+
+/**
+ * Load version asset index.
+ */
+loadVersionData = function(version, basePath, force = false){
+    return new Promise(function(fulfill, reject){
+        const name = version + '.json'
+        const url = 'https://s3.amazonaws.com/Minecraft.Download/versions/' + version + '/' + name
+        const versionPath = path.join(basePath, 'versions', version)
+        const versionFile = path.join(versionPath, name)
+        if(!fs.existsSync(versionFile) || force){
+            //This download will never be tracked as it's essential and trivial.
+            request.head(url, function(err, res, body){
+                console.log('Preparing download of ' + version + ' assets.')
+                mkpath.sync(versionPath)
+                const stream = request(url).pipe(fs.createWriteStream(versionFile))
+                stream.on('finish', function(){
+                    fulfill(JSON.parse(fs.readFileSync(versionFile)))
+                })
+            })
+        } else {
+            fulfill(JSON.parse(fs.readFileSync(versionFile)))
+        }
+    })
+}
+
+/**
+ * Public asset validation method.
+ */
+validateAssets = function(versionData, basePath, force = false){
+    return new Promise(function(fulfill, reject){
+        assetChainIndexData(versionData, basePath, force).then(() => {
+            fulfill()
+        })
+    })
+}
+
+//Chain the asset tasks to provide full async. The below functions are private.
+
+assetChainIndexData = function(versionData, basePath, force = false){
+    return new Promise(function(fulfill, reject){
+        //Asset index constants.
+        const assetIndex = versionData.assetIndex
+        const name = assetIndex.id + '.json'
+        const indexPath = path.join(basePath, 'assets', 'indexes')
+        const assetIndexLoc = path.join(indexPath, name)
+
+        let data = null
+        if(!fs.existsSync(assetIndexLoc) || force){
+            console.log('Downloading ' + versionData.id + ' asset index.')
+            mkpath.sync(indexPath)
+            const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc))
+            stream.on('finish', function() {
+                data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8'))
+                assetChainValidateAssets(versionData, basePath, data).then(() => {
+                    fulfill()
+                })
+            })
+        } else {
+            data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8'))
+            assetChainValidateAssets(versionData, basePath, data).then(() => {
+                fulfill()
+            })
+        }
+    })
+}
+
+assetChainValidateAssets = function(versionData, basePath, indexData){
+    return new Promise(function(fulfill, reject){
+
+        //Asset constants
+        const resourceURL = 'http://resources.download.minecraft.net/'
+        const localPath = path.join(basePath, 'assets')
+        const indexPath = path.join(localPath, 'indexes')
+        const objectPath = path.join(localPath, 'objects')
+
+        const assetDlQueue = []
+        let dlSize = 0;
+        //const objKeys = Object.keys(data.objects)
+        async.forEachOfLimit(indexData.objects, 10, function(value, key, cb){
+            const hash = value.hash
+            const assetName = path.join(hash.substring(0, 2), hash)
+            const urlName = hash.substring(0, 2) + "/" + hash
+            const ast = new Asset(key, hash, String(value.size), resourceURL + urlName, path.join(objectPath, assetName))
+            if(!validateLocal(ast.to, 'sha1', ast.hash)){
+                dlSize += (ast.size*1)
+                assetDlQueue.push(ast)
+            }
+            cb()
+        }, function(err){
+            instance.assets = new DLTracker(assetDlQueue, dlSize)
+            instance.totaldlsize += dlSize
+            fulfill()
+        })
+    })
+}
+
+/**
+ * Public library validation method.
+ */
+validateLibraries = function(versionData, basePath){
+    return new Promise(function(fulfill, reject){
+
+        const libArr = versionData.libraries
+        const libPath = path.join(basePath, 'libraries')
+
+        const libDlQueue = []
+        let dlSize = 0
+
+        //Check validity of each library. If the hashs don't match, download the library.
+        async.eachLimit(libArr, 5, function(lib, cb){
+            if(Library.validateRules(lib.rules)){
+                let artifact = (lib.natives == null) ? lib.downloads.artifact : lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()]]
+                const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, path.join(libPath, artifact.path))
+                if(!validateLocal(libItm.to, 'sha1', libItm.hash)){
+                    dlSize += (libItm.size*1)
+                    libDlQueue.push(libItm)
+                }
+            }
+            cb()
+        }, function(err){
+            instance.libraries = new DLTracker(libDlQueue, dlSize)
+            instance.totaldlsize += dlSize
+            fulfill()
+        })
+    })
+}
+
+runQueue = function(){
+    this.progress = 0;
+    let win = remote.getCurrentWindow()
+
+    //Start asset download
+    let assetacc = 0;
+    const concurrentAssetDlQueue = instance.assets.dlqueue.slice(0)
+    async.eachLimit(concurrentAssetDlQueue, 20, function(asset, cb){
+        mkpath.sync(path.join(asset.to, ".."))
+        let req = request(asset.from)
+        let writeStream = fs.createWriteStream(asset.to)
+        req.pipe(writeStream)
+        req.on('data', function(chunk){
+            instance.progress += chunk.length
+            assetacc += chunk.length
+            instance.emit('assetdata', assetacc)
+            console.log('Asset Progress', assetacc/instance.assets.dlsize)
+            win.setProgressBar(instance.progress/instance.totaldlsize)
+            //console.log('Total Progress', instance.progress/instance.totaldlsize)
+        })
+        writeStream.on('close', cb)
+    }, function(err){
+        if(err){
+            instance.emit('asseterr')
+            console.log('An asset failed to process');
+        } else {
+            instance.emit('assetdone')
+            console.log('All assets have been processed successfully')
+        }
+        instance.totaldlsize -= instance.assets.dlsize
+        instance.assets = new DLTracker([], 0)
+        win.setProgressBar(-1)
+    })
+
+    //Start library download
+    let libacc = 0
+    const concurrentLibraryDlQueue = instance.libraries.dlqueue.slice(0)
+    async.eachLimit(concurrentLibraryDlQueue, 1, function(lib, cb){
+        mkpath.sync(path.join(lib.to, '..'))
+        let req = request(lib.from)
+        let writeStream = fs.createWriteStream(lib.to)
+        req.pipe(writeStream)
+
+        req.on('data', function(chunk){
+            instance.progress += chunk.length
+            libacc += chunk.length
+            instance.emit('librarydata', libacc)
+            console.log('Library Progress', libacc/instance.libraries.dlsize)
+            win.setProgressBar(instance.progress/instance.totaldlsize)
+        })
+        writeStream.on('close', cb)
+    }, function(err){
+        if(err){
+            instance.emit('libraryerr')
+            console.log('A library failed to process');
+        } else {
+            instance.emit('librarydone')
+            console.log('All libraries have been processed successfully');
+        }
+        instance.totaldlsize -= instance.libraries.dlsize
+        instance.libraries = new DLTracker([], 0)
+        win.setProgressBar(-1)
+    })
+}
+
+module.exports = {
+    loadVersionData,
+    validateAssets,
+    validateLibraries,
+    runQueue,
+    instance
+}

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

@@ -1,37 +0,0 @@
-
-exports.mojangFriendlyOS = function(){
-    const opSys = process.platform
-    if (opSys === 'darwin') {
-        return 'osx';
-    } else if (opSys === 'win32'){
-        return 'windows';
-    } else if (opSys === 'linux'){
-        return 'linux';
-    } else {
-        return 'unknown_os';
-    }
-}
-
-exports.validateRules = function(rules){
-    if(rules == null) return true
-
-    let result = true
-    rules.forEach(function(rule){
-        const action = rule['action']
-        const osProp = rule['os']
-        if(action != null){
-            if(osProp != null){
-                 const osName = osProp['name']
-                 const osMoj = exports.mojangFriendlyOS()
-                 if(action === 'allow'){
-                     result = osName === osMoj
-                     return
-                 } else if(action === 'disallow'){
-                     result = osName !== osMoj
-                     return
-                 }
-            }
-        }
-    })
-    return result
-}

+ 14 - 1
app/assets/js/script.js

@@ -1,13 +1,26 @@
 var $ = require('jQuery');
 var $ = require('jQuery');
 const remote = require('electron').remote
 const remote = require('electron').remote
 const shell = require('electron').shell
 const shell = require('electron').shell
+const path = require('path')
 
 
 /* Open web links in the user's default browser. */
 /* Open web links in the user's default browser. */
 $(document).on('click', 'a[href^="http"]', function(event) {
 $(document).on('click', 'a[href^="http"]', function(event) {
     event.preventDefault();
     event.preventDefault();
-    shell.openExternal(this.href);
+    //testdownloads()
+    shell.openExternal(this.href)
 });
 });
 
 
+testdownloads = async function(){
+    const ag = require(path.join(__dirname, 'assets', 'js', 'assetguard.js'))
+    const basePath = path.join(__dirname, '..', 'mcfiles')
+    let versionData = await ag.loadVersionData('1.11.2', basePath)
+    await ag.validateAssets(versionData, basePath)
+    console.log('assets done')
+    await ag.validateLibraries(versionData, basePath)
+    console.log('libs done')
+    ag.runQueue()
+}
+
 /*Opens DevTools window if you type "wcdev" in sequence.
 /*Opens DevTools window if you type "wcdev" in sequence.
   This will crash the program if you are using multiple
   This will crash the program if you are using multiple
   DevTools, for example the chrome debugger in VS Code. */
   DevTools, for example the chrome debugger in VS Code. */

+ 0 - 13
index.js

@@ -17,19 +17,6 @@ function createWindow() {
 
 
     win.setMenu(null)
     win.setMenu(null)
 
 
-    /*//Code for testing, marked for removal one it's properly implemented.
-    const assetdl = require('./app/assets/js/assetdownload.js')
-    const basePath = path.join(__dirname, 'mcfiles')
-    const dataPromise = assetdl.parseVersionData('1.11.2', basePath)
-    dataPromise.then(function(data){
-        assetdl.downloadAssets(data, basePath)
-        //assetdl.downloadAssets(data, basePath)
-        //assetdl.downloadClient(data, basePath)
-        //assetdl.downloadLogConfig(data, basePath)
-        //assetdl.downloadLibraries(data, basePath)
-        //require('./app/assets/js/launchprocess.js').launchMinecraft(data, basePath)
-    })*/
-
     win.on('closed', () => {
     win.on('closed', () => {
         win = null
         win = null
     })
     })