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