All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.minecraftforge.gradle.tasks.DownloadAssetsTask Maven / Gradle / Ivy

/*
 * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins.
 * Copyright (C) 2013-2019 Minecraft Forge
 * Copyright (C) 2020-2021 anatawa12 and other contributors
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 * USA
 */
package net.minecraftforge.gradle.tasks;

import groovy.lang.Closure;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import net.minecraftforge.gradle.common.Constants;
import net.minecraftforge.gradle.util.delayed.DelayedFile;
import net.minecraftforge.gradle.util.json.JsonFactory;
import net.minecraftforge.gradle.util.json.version.AssetIndex;
import net.minecraftforge.gradle.util.json.version.AssetIndex.AssetEntry;

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.Files;

public class DownloadAssetsTask extends DefaultTask
{
    DelayedFile           assetsDir;

    Object                assetIndex;

    private File          virtualRoot  = null;
    private final File    minecraftDir = new File(Constants.getMinecraftDirectory(), "assets/objects");

    private static final int MAX_TRIES = 5;

    @TaskAction
    public void doTask() throws IOException, InterruptedException
    {
        File outDir = new File(getAssetsDir(), "objects");
        if (!outDir.exists() || !outDir.isDirectory())
        {
            outDir.mkdirs();
        }

        File indexFile = getAssetsIndex();
        AssetIndex index = JsonFactory.loadAssetsIndex(indexFile);

        // check virtual
        if (index.virtual)
        {
            virtualRoot = new File(getAssetsDir(), "virtual/" + Files.getNameWithoutExtension(indexFile.getName()));
            virtualRoot.mkdirs();
        }

        // make thread pool
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2);

        for (Entry e : index.objects.entrySet())
        {
            Asset asset = new Asset(e.getKey(), e.getValue().hash, e.getValue().size);
            executor.submit(new GetAssetTask(asset, outDir, minecraftDir, virtualRoot));
        }

        executor.shutdown(); // complete all the tasks then shutdown.

        int max = (int) executor.getTaskCount(); // its gonna be somewhere around 600-700 I think

        // as long as the excutor isnt dead yet.
        while (!executor.awaitTermination(1, TimeUnit.SECONDS))
        {
            int done = (int) executor.getCompletedTaskCount();
            getLogger().lifecycle("Current status: {}/{}   {}%", done, max, (int) ((double) done / max * 100));
        }
    }

    public File getAssetsDir()
    {
        return assetsDir.call();
    }

    public void setAssetsDir(DelayedFile assetsDir)
    {
        this.assetsDir = assetsDir;
    }

    public File getAssetsIndex()
    {
        return getProject().file(assetIndex);
    }

    public void setAssetsIndex(Closure index)
    {
        this.assetIndex = index;
    }

    private static class Asset
    {
        public final String name;
        public final String path;
        public final String hash;
        public final long   size;

        Asset(String name, String hash, long size)
        {
            this.name = name;
            this.path = hash.substring(0, 2) + "/" + hash;
            this.hash = hash.toLowerCase();
            this.size = size;
        }
    }
    private static boolean checkFileCorrupt(File file, long size, String expectedHash)
    {
        if (!file.exists())
            return true;

        if (file.length() != size)
            return true;

        if (!expectedHash.equalsIgnoreCase(Constants.hash(file, "SHA1")))
            return true;

        return false;
    }

    private static class GetAssetTask implements Callable
    {
        private static final Logger LOGGER = LoggerFactory.getLogger(GetAssetTask.class);
        private final Asset asset;
        private final File assetDir, minecraftDir, virtualRoot;

        private GetAssetTask(Asset asset, File assetDir, File minecraftDir, File virtualRoot)
        {
            this.asset = asset;
            this.assetDir = assetDir;
            this.minecraftDir = minecraftDir;
            this.virtualRoot = virtualRoot;
        }

        @Override
        public Boolean call()
        {
            boolean worked = true;

            for (int tryNum = 1; tryNum < MAX_TRIES + 1; tryNum++)
            {
                try
                {
                    File file = new File(assetDir, asset.path);

                    if (checkFileCorrupt(file, asset.size, asset.hash))
                    {
                        file.delete();
                    }

                    // if it exists, its good, so we dont do this stuff...
                    if (!file.exists())
                    {
                        file.getParentFile().mkdirs();
                        File localMc = new File(minecraftDir, asset.path);

                        if (checkFileCorrupt(localMc, asset.size, asset.hash))
                        {
                            // download
                            ReadableByteChannel channel = Channels.newChannel(new URL(Constants.URL_ASSETS + "/" + asset.path).openStream());
                            try (FileOutputStream fout = new FileOutputStream(file);
                                 FileChannel fileChannel = fout.getChannel())
                            {
                                fileChannel.transferFrom(channel, 0, asset.size);
                            }
                        }
                        else
                        {
                            // copy from MC
                            Constants.copyFile(localMc, file, asset.size);
                        }
                    }


                    if (virtualRoot != null)
                    {
                        File virtual = new File(virtualRoot, asset.name);

                        if (checkFileCorrupt(virtual, asset.size, asset.hash))
                        {
                            virtual.delete();
                            Constants.copyFile(file, virtual);
                        }
                    }

                }
                catch (Exception e)
                {
                    LOGGER.error("Error downloading asset (try {}) : {}", tryNum, asset.name);
                    e.printStackTrace();
                    worked = false;
                }
            }

            return worked;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy