package php.runtime.ext.core.classes.lib;
import php.runtime.Information;
import php.runtime.Memory;
import php.runtime.annotation.Reflection.Name;
import php.runtime.annotation.Reflection.Nullable;
import php.runtime.annotation.Reflection.Signature;
import php.runtime.annotation.Runtime.FastMethod;
import php.runtime.common.DigestUtils;
import php.runtime.env.Environment;
import php.runtime.ext.core.classes.stream.FileObject;
import php.runtime.ext.core.classes.stream.Stream;
import php.runtime.invoke.Invoker;
import php.runtime.lang.BaseObject;
import php.runtime.lang.ForeachIterator;
import php.runtime.memory.*;
import php.runtime.reflection.ClassEntity;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Name("php\\lib\\fs")
public class FsUtils extends BaseObject {
public static final int BUFFER_SIZE = 8192;
private final static char CHAR_UNDEFINED = 0xFFFF;
private final static Set<String> winSystemNames = new HashSet<>(Arrays.asList("CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"));
private final static char[] deniedNameChars = {'*', '?', '"', '>', '<', '|', ':', '/', '\\'};
public FsUtils(Environment env, ClassEntity clazz) {
super(env, clazz);
}
@Signature
private Memory __construct(Environment env, Memory... args) { return Memory.NULL; }
private static boolean isPrintableChar( char c ) {
Character.UnicodeBlock block = Character.UnicodeBlock.of( c );
return (!Character.isISOControl(c)) &&
c != CHAR_UNDEFINED &&
block != null &&
block != Character.UnicodeBlock.SPECIALS;
}
@Signature
public static String abs(String path) {
try {
return new File(path).getCanonicalPath();
} catch (IOException e) {
return new File(path).getAbsolutePath();
}
}
@Signature
public static boolean valid(String name) {
if (name.trim().isEmpty()) {
return false;
}
for (char c : deniedNameChars) {
if (name.indexOf(c) > -1) {
return false;
}
}
if (name.equals(".") || name.startsWith("./") || name.startsWith(".\\")) {
return false;
}
int length = name.length();
boolean onlySpecs = true;
for (int i = 0; i < length; i++) {
char ch = name.charAt(i);
if (ch != '.' && ch != '/' && ch != '\\') {
onlySpecs = false;
}
if (!isPrintableChar(ch)) {
return false;
}
}
if (onlySpecs) {
return false;
}
if (System.getProperty("os.name").toLowerCase().contains("win")) {
if (winSystemNames.contains(name.toUpperCase())) {
return false;
}
}
return true;
}
@Signature
public static String name(String path) {
return new File(path).getName();
}
@Signature
public static String ext(String path) {
String name = new File(path).getName();
int indexOf = name.lastIndexOf('.');
if (indexOf > -1) {
return name.substring(indexOf + 1);
} else {
return null;
}
}
@Signature
public static boolean hasExt(Environment env, String path) {
return ext(path) != null;
}
@Signature
public static boolean hasExt(Environment env, String path, Memory extensions) {
return hasExt(env, path, extensions, true);
}
@Signature
public static boolean hasExt(Environment env, String path, Memory extensions, boolean ignoreCase) {
Set<String> exts = new HashSet<>();
if (extensions.isTraversable()) {
ForeachIterator iterator = extensions.getNewIterator(env);
while (iterator.next()) {
String value = iterator.getValue().toString();
if (ignoreCase) {
value = value.toLowerCase();
}
exts.add(value);
}
} else {
exts.add(ignoreCase ? extensions.toString().toLowerCase() : extensions.toString());
}
String ext = ext(path);
if (ignoreCase && ext != null) {
ext = ext.toLowerCase();
}
return exts.contains(ext);
}
@Signature
public static String nameNoExt(String path) {
String name = new File(path).getName();
int indexOf = name.lastIndexOf('.');
if (indexOf > -1) {
name = name.substring(0, indexOf);
}
return name;
}
@Signature
public static String pathNoExt(String path) {
String name = new File(path).getPath();
int indexOf = name.lastIndexOf('.');
if (indexOf > -1) {
name = name.substring(0, indexOf);
}
return name;
}
@Signature
public static long size(String path) {
return new File(path).length();
}
@Signature
public static long time(String path) {
return new File(path).lastModified();
}
@Signature
public static boolean isFile(String path) {
return new File(path).isFile();
}
@Signature
public static boolean isDir(String path) {
return new File(path).isDirectory();
}
@Signature
public static boolean isHidden(String path) {
return new File(path).isHidden();
}
@Signature
public static String normalize(String path) {
return new File(path).getPath();
}
@Signature
public static String parent(String path) {
return new File(path).getParent();
}
@Signature
public static boolean ensureParent(String path) {
File parent = new File(path).getParentFile();
if (parent == null) {
return true;
}
if (!parent.isDirectory()) {
return parent.mkdirs();
}
return true;
}
@Signature
public static boolean exists(String path) {
return new File(path).exists();
}
@Signature
@FastMethod
public static Memory separator(Environment env, Memory... args) {
return StringMemory.valueOf(File.separator);
}
@Signature
@FastMethod
public static Memory pathSeparator(Environment env, Memory... args) {
return StringMemory.valueOf(File.pathSeparator);
}
@Signature
public static boolean makeDir(String path) {
return new File(path).mkdirs();
}
@Signature
public static boolean makeFile(String path) {
String parent = parent(path);
if (parent != null && !isDir(path)) {
if (!makeDir(parent)) {
return false;
}
}
try {
return new File(path).createNewFile();
} catch (IOException e) {
return false;
}
}
@Signature
public static boolean delete(String path) {
return new File(path).delete();
}
@Signature
public static Memory get(Environment env, String input) throws Throwable {
return get(env, input, null, "r");
}
@Signature
public static Memory get(Environment env, String input, @Nullable String charset) throws Throwable {
return get(env, input, charset, "r");
}
@Signature
public static Memory get(Environment env, String input, @Nullable String charset, String mode) throws Throwable {
Stream stream = Stream.create(env, input, mode);
try {
Memory memory = env.invokeMethod(stream, "readFully");
if (charset == null || charset.trim().isEmpty()) {
return memory;
} else {
return StrUtils.decode(env, memory, StringMemory.valueOf(charset));
}
} finally {
env.invokeMethod(stream, "close");
}
}
@Signature
public static long copy(InputStream input, OutputStream output) throws IOException {
return copy(input, output, null, BUFFER_SIZE);
}
@Signature
public static long copy(InputStream input, OutputStream output, @Nullable Invoker callback) throws IOException {
return copy(input, output, callback, BUFFER_SIZE);
}
@Signature
public static long copy(InputStream input, OutputStream output, @Nullable Invoker callback, int bufferSize) throws IOException {
long nread = 0L;
byte[] buf = new byte[bufferSize];
int n;
BufferedInputStream inputStream = new BufferedInputStream(input, bufferSize);
BufferedOutputStream outputStream = new BufferedOutputStream(output, bufferSize);
while ((n = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, n);
nread += n;
if (callback != null) {
if (callback.callAny(nread, n).toValue() == Memory.FALSE) {
break;
}
}
}
outputStream.flush();
return nread;
}
@Signature
public static Memory hash(InputStream source) throws NoSuchAlgorithmException {
return hash(source, "MD5", null);
}
@Signature
public static Memory hash(InputStream source, String algo) throws NoSuchAlgorithmException {
return hash(source, algo, null);
}
@Signature
public static Memory hash(InputStream source, String algo, @Nullable Invoker invoker) throws NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance(algo);
byte[] buffer = new byte[BUFFER_SIZE];
int len;
try {
int sum = 0;
while ((len = source.read(buffer)) > 0) {
messageDigest.update(buffer, 0, len);
sum += len;
if (invoker != null) {
if (invoker.callAny(sum, len).toValue() == Memory.FALSE) {
break;
}
}
}
return StringMemory.valueOf(DigestUtils.bytesToHex(messageDigest.digest()));
} catch (FileNotFoundException e) {
return Memory.NULL;
} catch (IOException e) {
return Memory.NULL;
}
}
public static void scan(String path, ScanProgressHandler scanProgressHandler) {
scan(path, scanProgressHandler, 0);
}
public static void scan(String path, final ScanProgressHandler scanProgressHandler, final int maxDepth) {
scan(path, scanProgressHandler, maxDepth, 1);
}
public static void scan(String path, final ScanProgressHandler scanProgressHandler, final int maxDepth, final int _depth) {
if (maxDepth > 0 && _depth > maxDepth) {
return;
}
File file = new File(path);
file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (!scanProgressHandler.isSubIsFirst()) {
scanProgressHandler.call(pathname, _depth);
}
if (pathname.isDirectory()) {
scan(pathname.getPath(), scanProgressHandler, maxDepth, _depth + 1);
}
if (scanProgressHandler.isSubIsFirst()) {
scanProgressHandler.call(pathname, _depth);
}
return false;
}
});
}
@Signature
public static void scan(String path, final Invoker progress) {
scan(path, progress, 0);
}
@Signature
public static void scan(String path, final Invoker progress, int maxDepth) {
scan(path, progress, maxDepth, false);
}
@Signature
public static void scan(String path, final Invoker progress, int maxDepth, boolean filesIsFirst) {
scan(path, new ScanProgressHandler(filesIsFirst) {
@Override
public boolean call(File pathname, int depth) {
progress.callAny(pathname, depth);
return true;
}
}, maxDepth);
}
@Signature
public static ArrayMemory clean(final Environment env, String path) {
return clean(env, path, null);
}
@Signature
public static ArrayMemory clean(final Environment env, String path, @Nullable final Invoker checker) {
ArrayMemory result = new ArrayMemory();
final ArrayMemory success = new ArrayMemory();
final ArrayMemory error = new ArrayMemory();
final ArrayMemory skip = new ArrayMemory();
result.put("success", success);
result.put("error", error);
result.put("skip", error);
scan(path, new ScanProgressHandler(true) {
@Override
public boolean call(File file, int depth) {
Memory value = ObjectMemory.valueOf(new FileObject(env, file));
if (checker == null || checker.callAny(file, depth).toBoolean()) {
if (delete(file.getPath())) {
success.add(value);
} else {
error.add(value);
}
} else {
skip.add(value);
}
return true;
}
});
return result.toConstant();
}
@Signature
public static boolean rename(String path, String newName) {
String parent = parent(path);
return new File(path).renameTo(new File((parent != null ? parent + File.separator : "") + newName));
}
@Signature
public static boolean move(String path, String newPath) {
return new File(path).renameTo(new File(newPath));
}
public abstract static class ScanProgressHandler {
protected final boolean subIsFirst;
protected ScanProgressHandler(boolean subIsFirst) {
this.subIsFirst = subIsFirst;
}
public boolean isSubIsFirst() {
return subIsFirst;
}
abstract public boolean call(File file, int depth);
}
}