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

org.robovm.compiler.util.io.RamDiskTools Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 RoboVM AB
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.robovm.compiler.util.io;

import com.dd.plist.NSDictionary;
import com.dd.plist.NSNumber;
import com.dd.plist.NSObject;
import com.dd.plist.PropertyListParser;
import org.apache.commons.io.FileUtils;
import org.robovm.compiler.config.Arch;
import org.robovm.compiler.config.Config;
import org.robovm.compiler.config.OS;
import org.robovm.compiler.log.Logger;
import org.robovm.compiler.util.Executor;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

/**
 * Will modify cache and tmpdir paths given a {@link Config#builder()} and prun
 * the cache if necessary.
 *
 */
public class RamDiskTools {
    private static final String ROBOVM_RAM_DISK_PATH = "/Volumes/RoboVM RAM Disk";
    private static final long MIN_FREE_SPACE = 1024 * 1024 * 400;

    private File newCacheDir;
    private File newTmpDir;

    public File getCacheDir() {
        return newCacheDir;
    }

    public File getTmpDir() {
        return newTmpDir;
    }

    /**
     * Checks if a RAM disk is available and prunes it if necessary.
     */
    public void setupRamDisk(Config config, File cacheDir, File tmpDir) {
        this.newCacheDir = cacheDir;
        this.newTmpDir = tmpDir;

        if (OS.getDefaultOS() != OS.macosx) {
            return;
        }

        File volume = new File(ROBOVM_RAM_DISK_PATH);
        if (!volume.exists()) {
            try {
                FileStore store = Files.getFileStore(new File(System.getProperty("user.home"))
                        .toPath());
                
                String plist = new Executor(Logger.NULL_LOGGER, "diskutil").args("info", "-plist", store.name())
                        .execCapture();
                NSDictionary dict = (NSDictionary)PropertyListParser.parse(plist.getBytes("UTF-8"));
                NSObject value = dict.objectForKey("SolidState");
                if(value == null || (value instanceof NSNumber && !((NSNumber)value).boolValue())) {
                    // @formatter:off
                    config.getLogger().warn("RoboVM has detected that you are running on a slow HDD. Please consider mounting a RAM disk.\n" +
                                            "To create a 2GB RAM disk, run this in your terminal:\n" +
                                            "SIZE=2048 ; diskutil erasevolume HFS+ 'RoboVM RAM Disk' `hdiutil attach -nomount ram://$((SIZE * 2048))`\n" +
                                            "See http://docs.robovm.com/ for more info");
                    // @formatter:on
                }
            } catch (Throwable t) {
                // nothing to do here, can't decide if we are on a SSD or HDD
                t.printStackTrace();
            }
            return;
        }

        try {
            FileStore store = Files.getFileStore(volume.toPath());
            if (store.getUsableSpace() < MIN_FREE_SPACE) {
                cleanRamDisk(store, volume, config);
                if (store.getUsableSpace() < MIN_FREE_SPACE) {
                    config.getLogger().info("Couldn't free enough space on RAM disk, using hard drive");
                    return;
                }
            }

            File newCacheDir = new File(volume, "cache");
            if (!newCacheDir.exists() && !newCacheDir.mkdirs()) {
                config.getLogger().info("Couldn't create cache directory on RAM disk, using hard drive");
                return;
            }

            // manage every build in own tmp folder -- allows to clear up not required tmp data
            File newTmpDir = new File(volume, "tmp");
            newTmpDir = new File(newTmpDir, config.getBuildUuid().toString());
            if (tmpDir.getAbsolutePath().startsWith(newTmpDir.getAbsolutePath())) {
                // tmpDir already build on top of RamDisk, don't add it
                // happens when building slice config from main one
                newTmpDir = tmpDir;
            } else {
                newTmpDir = new File(newTmpDir, tmpDir.getAbsolutePath());
            }
            if (!newTmpDir.exists() && !newTmpDir.mkdirs()) {
                config.getLogger().info("Couldn't create tmp directory on RAM disk, using hard drive");
                return;
            }
            config.getLogger().info("Using RAM disk at %s for cache and tmp directory", ROBOVM_RAM_DISK_PATH);
            this.newCacheDir = newCacheDir;
            this.newTmpDir = newTmpDir;
        } catch (Throwable t) {
            config.getLogger().error("Couldn't setup RAM disk, using hard drive, %s", t.getMessage());
            this.newCacheDir = cacheDir;
            this.newTmpDir = tmpDir;
        }
    }

    private void cleanRamDisk(FileStore store, File volume, Config config) {
        // clean tmp/ dir
        try {
            cleanTmp(volume, config);
        } catch (IOException e) {
            // nothing to do here
        }

        // clean the cache/
        try {
            // only clean the cache if killing the tmp dir didn't work
            if (store.getUsableSpace() < MIN_FREE_SPACE) {
                cleanCache(store, volume, config);
            }
        } catch (IOException e) {
            // nothing to do here
        }
    }

    private void cleanTmp(File volume, Config config) throws IOException {
        // clean up all files/folders inside tmp but not current build ones
        File tmpDir = new File(volume, "tmp");
        if (tmpDir.exists() && tmpDir.isDirectory()) {
            File[] builds = tmpDir.listFiles();
            if (builds != null && builds.length > 0) {
                String currentBuild = config.getBuildUuid().toString();
                for (File b : builds) {
                    if (!currentBuild.equals(b.getName()))
                        if (b.isDirectory())
                            FileUtils.deleteDirectory(b);
                        else
                            b.delete();
                }
            }
        }
    }

    private void cleanCache(FileStore store, File volume, Config config) throws IOException {
        OS currOs = config.getOs();
        Arch currArch = config.getArch();
        CacheDir currCacheDir = constructCacheDir(volume, currOs, currArch, config.isDebug());

        // Enumerate all directories that are not our current cache
        // dir
        List cacheDirs = new ArrayList<>();
        for (OS os : OS.values()) {
            for (Arch arch : Arch.supported(os)) {
                for (boolean isDebug : new boolean[]{false, true}) {
                    CacheDir cacheDir = constructCacheDir(volume, os, arch, isDebug);
                    if (cacheDir != null && (currCacheDir == null || !cacheDir.directory.equals(currCacheDir.directory))) {
                        cacheDirs.add(cacheDir);
                    }
                }
            }
        }
        
        // sort the directories by their last modified
        // date in ascending order (oldest first). We
        // start deleting  the oldest cache files first
        // before we start deleting the cache files for
        // the current os/arch
        Collections.sort(cacheDirs, new Comparator() {
            @Override
            public int compare(CacheDir o1, CacheDir o2) {
                return new Date(o1.lastModified).compareTo(new Date(o2.lastModified));
            }
        });

        // add our current target dir last if it already
        // exists. This way we delete its cache files last
        if(currCacheDir != null) {
            cacheDirs.add(currCacheDir);
        }
        
        // start deleting files until we have enough
        // space
        for(CacheDir dir: cacheDirs) {
            for(File file: dir.objFiles) {
                file.delete();
                if(store.getUsableSpace() > MIN_FREE_SPACE) {
                    return;
                }
            }            
        }                
        
        // nuclear option, we couldn't delete enough files
        FileUtils.deleteDirectory(new File(volume, "cache"));
    }

    private CacheDir constructCacheDir(File volume, OS os, Arch arch, boolean isDebug) {
        File dir = new File(volume, "cache/" + os.toString() + "/" + arch.toString() + "/"
                + (isDebug ? "debug" : "release"));
        if (!dir.exists())
            return null;
        List objFiles = new ArrayList<>(FileUtils.listFiles(dir, new String[]{"o"},
                true));
        objFiles.sort((f1, f2) -> new Date(f2.lastModified()).compareTo(new Date(f1.lastModified())));
        long lastModified = 0;
        for (File file : objFiles) {
            lastModified = Math.max(lastModified, file.lastModified());
        }
        return new CacheDir(dir, objFiles, lastModified);
    }

    static class CacheDir {
        File directory;
        List objFiles;
        long lastModified;

        public CacheDir(File directory, List objFiles, long lastModified) {
            this.directory = directory;
            this.objFiles = objFiles;
            this.lastModified = lastModified;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy