diff --git a/mirai-console/backend/mirai-console/src/MiraiConsole.kt b/mirai-console/backend/mirai-console/src/MiraiConsole.kt index 4d81a849e5..badb9c70d2 100644 --- a/mirai-console/backend/mirai-console/src/MiraiConsole.kt +++ b/mirai-console/backend/mirai-console/src/MiraiConsole.kt @@ -34,7 +34,7 @@ import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.* import java.io.File -import java.nio.file.Path +import com.llamalab.safs.Path import java.time.Instant /** diff --git a/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt b/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt index 751a3884a7..c8466c2e39 100644 --- a/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt +++ b/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt @@ -40,7 +40,7 @@ import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.* -import java.nio.file.Path +import com.llamalab.safs.Path import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.annotation.AnnotationTarget.* diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/AccessDeniedException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/AccessDeniedException.java new file mode 100644 index 0000000000..97eed5f360 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/AccessDeniedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class AccessDeniedException extends FileSystemException { + + public AccessDeniedException (String file) { + super(file); + } + + public AccessDeniedException (String file, String otherFile, String reason) { + super(file, otherFile, reason); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/AtomicMoveNotSupportedException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/AtomicMoveNotSupportedException.java new file mode 100644 index 0000000000..defa48873b --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/AtomicMoveNotSupportedException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class AtomicMoveNotSupportedException extends FileSystemException { + + public AtomicMoveNotSupportedException (String source, String target, String reason) { + super(source, target, reason); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/ClosedFileSystemException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ClosedFileSystemException.java new file mode 100644 index 0000000000..14ba84d3a6 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ClosedFileSystemException.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class ClosedFileSystemException extends IllegalStateException { +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/ClosedWatchServiceException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ClosedWatchServiceException.java new file mode 100644 index 0000000000..03bfa50216 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ClosedWatchServiceException.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class ClosedWatchServiceException extends IllegalStateException {} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/CopyOption.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/CopyOption.java new file mode 100644 index 0000000000..e9413ef679 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/CopyOption.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public interface CopyOption { +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryIteratorException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryIteratorException.java new file mode 100644 index 0000000000..f37b33a716 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryIteratorException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.io.IOException; +import java.util.ConcurrentModificationException; + +@SuppressWarnings("serial") +public class DirectoryIteratorException extends ConcurrentModificationException { + + public DirectoryIteratorException (IOException cause) { + initCause(cause); + } + + @Override + public final IOException getCause () { + return (IOException)super.getCause(); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryNotEmptyException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryNotEmptyException.java new file mode 100644 index 0000000000..7ac91fe0b9 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryNotEmptyException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class DirectoryNotEmptyException extends FileSystemException { + + public DirectoryNotEmptyException (String file) { + super(file); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryStream.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryStream.java new file mode 100644 index 0000000000..ffe8a78ffc --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/DirectoryStream.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.io.Closeable; +import java.io.IOException; + +public interface DirectoryStream extends Iterable, Closeable { + public interface Filter { + public boolean accept (T entry) throws IOException; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileAlreadyExistsException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileAlreadyExistsException.java new file mode 100644 index 0000000000..53667a6e31 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileAlreadyExistsException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class FileAlreadyExistsException extends FileSystemException { + + public FileAlreadyExistsException (String file) { + super(file); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileStore.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileStore.java new file mode 100644 index 0000000000..787024cb2d --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileStore.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.io.IOException; + +public abstract class FileStore { + protected FileStore () {} + public abstract String name (); + public abstract String type (); + public abstract boolean isReadOnly (); + public abstract long getTotalSpace () throws IOException; + public abstract long getUsableSpace () throws IOException; + public abstract long getUnallocatedSpace () throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystem.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystem.java new file mode 100644 index 0000000000..b6782f73d2 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystem.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import com.llamalab.safs.attribute.UserPrincipalLookupService; +import com.llamalab.safs.spi.FileSystemProvider; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Set; + +public abstract class FileSystem implements Closeable { + public abstract FileSystemProvider provider (); + public abstract boolean isOpen (); + public abstract boolean isReadOnly (); + public abstract Set supportedFileAttributeViews(); + public abstract String getSeparator (); + public abstract Path getPath (String first, String... more); + public abstract PathMatcher getPathMatcher (String syntaxAndPattern); + public abstract Iterable getFileStores (); + public abstract Iterable getRootDirectories (); + public abstract UserPrincipalLookupService getUserPrincipalLookupService (); + public abstract WatchService newWatchService () throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemAlreadyExistsException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemAlreadyExistsException.java new file mode 100644 index 0000000000..c1d319a667 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemAlreadyExistsException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class FileSystemAlreadyExistsException extends RuntimeException { + + public FileSystemAlreadyExistsException () {} + + public FileSystemAlreadyExistsException (String message) { + super(message); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemException.java new file mode 100644 index 0000000000..c677dfee52 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemException.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class FileSystemException extends IOException { + + private final String file; + private final String otherFile; + + public FileSystemException (String file) { + this(file, null, null); + } + + public FileSystemException (String file, String otherFile, String reason) { + super(reason); + this.file = file; + this.otherFile = otherFile; + } + + public String getFile () { + return file; + } + + public String getOtherFile () { + return otherFile; + } + + public String getReason () { + return super.getMessage(); + } + + @Override + public String getMessage() { + if (file == null && otherFile == null) + return getReason(); + final StringBuilder sb = new StringBuilder(); + if (file != null) + sb.append(file); + if (otherFile != null) + sb.append(" -> ").append(otherFile); + if (getReason() != null) + sb.append(": ").append(getReason()); + return sb.toString(); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemLoopException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemLoopException.java new file mode 100644 index 0000000000..5cd59e3bc4 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemLoopException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class FileSystemLoopException extends FileSystemException { + + public FileSystemLoopException (String file) { + super(file); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemNotFoundException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemNotFoundException.java new file mode 100644 index 0000000000..962e589aa8 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystemNotFoundException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class FileSystemNotFoundException extends RuntimeException { + + public FileSystemNotFoundException () {} + + public FileSystemNotFoundException (String message) { + super(message); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystems.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystems.java new file mode 100644 index 0000000000..ed5c0724bf --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileSystems.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import com.llamalab.safs.java.DefaultJavaFileSystemProvider; +import com.llamalab.safs.spi.FileSystemProvider; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; + +public final class FileSystems { + + private FileSystems () {} + + public static FileSystem getDefault () { + return DefaultFileSystemHolder.fileSystem; + } + + public static FileSystem getFileSystem (URI uri) { + for (final FileSystemProvider provider : FileSystemProvider.installedProviders()) { + if (provider.getScheme().equalsIgnoreCase(uri.getScheme())) + return provider.getFileSystem(uri); + } + throw new ProviderNotFoundException(uri.getScheme()); + } + + public static FileSystem newFileSystem (URI uri, Map env) throws IOException { + for (final FileSystemProvider provider : FileSystemProvider.installedProviders()) { + if (provider.getScheme().equalsIgnoreCase(uri.getScheme())) + return provider.newFileSystem(uri, env); + } + throw new ProviderNotFoundException(uri.getScheme()); + } + + + private static final class DefaultFileSystemHolder { + + static final FileSystem fileSystem = loadDefaultProvider().getFileSystem(URI.create("file:///")); + + private static FileSystemProvider loadDefaultProvider () { + FileSystemProvider provider = new DefaultJavaFileSystemProvider(); + final String value = System.getProperty("com.llamalab.safs.spi.DefaultFileSystemProvider"); + if (value != null) { + try { + for (final String className : value.split(",")) { + provider = (FileSystemProvider)Class.forName(className) + .getDeclaredConstructor(FileSystemProvider.class) + .newInstance(provider); + } + } + catch (Throwable t) { + throw new Error(t); + } + } + return provider; + } + + } // class DefaultFileSystemHolder + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitOption.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitOption.java new file mode 100644 index 0000000000..1ddef8a490 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitOption.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public enum FileVisitOption { + /** Follow symbolic links. */ + FOLLOW_LINKS, +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitResult.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitResult.java new file mode 100644 index 0000000000..82d42f113a --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitResult.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public enum FileVisitResult { + CONTINUE, + TERMINATE, + SKIP_SUBTREE, + SKIP_SIBLINGS, +} \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitor.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitor.java new file mode 100644 index 0000000000..c385e7ee45 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/FileVisitor.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import com.llamalab.safs.attribute.BasicFileAttributes; + +import java.io.IOException; + +public interface FileVisitor { + public FileVisitResult preVisitDirectory (T dir, BasicFileAttributes attrs) throws IOException; + public FileVisitResult postVisitDirectory (T dir, IOException e) throws IOException; + public FileVisitResult visitFile (T file, BasicFileAttributes attrs) throws IOException; + public FileVisitResult visitFileFailed (T file, IOException e) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/Files.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Files.java new file mode 100644 index 0000000000..ccbcf50b00 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Files.java @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import com.llamalab.safs.attribute.BasicFileAttributes; +import com.llamalab.safs.attribute.FileAttribute; +import com.llamalab.safs.attribute.FileAttributeView; +import com.llamalab.safs.attribute.FileTime; +import com.llamalab.safs.internal.BasicFileAttribute; +import com.llamalab.safs.internal.DefaultFileSystem; +import com.llamalab.safs.internal.SearchSet; +import com.llamalab.safs.internal.Utils; +import com.llamalab.safs.channels.SeekableByteChannel; +import com.llamalab.safs.spi.FileSystemProvider; +import com.llamalab.safs.spi.FileTypeDetector; +import org.apache.commons.lang3.NotImplementedException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.URLConnection; +import java.nio.charset.Charset; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; + +public final class Files { + + //private static final OpenOption[] COPY_REPLACE_EXISTING = { StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE }; + //private static final OpenOption[] COPY_KEEP_EXISTING = { StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW }; + private static final LinkOption[] LINK_NOFOLLOW_LINKS = { LinkOption.NOFOLLOW_LINKS }; + private static final Set VISIT_EMPTY = EnumSet.noneOf(FileVisitOption.class); + + private static final String COPIED_ATTRIBUTES = BasicFileAttribute.lastModifiedTime + "," + BasicFileAttribute.lastAccessTime + "," + BasicFileAttribute.creationTime; + + private static final SecureRandom TEMP_RAND = new SecureRandom(); + + private Files () {} + + public static FileStore getFileStore (Path path) throws IOException { + return provider(path).getFileStore(path); + } + + public static void delete (Path path) throws IOException { + provider(path).delete(path); + } + + private static void deleteQuietly (Path path) { + try { + delete(path); + } + catch (Throwable t) { + // ignore + } + } + public static boolean deleteIfExists (Path path) throws IOException { + return provider(path).deleteIfExists(path); + } + + public static Path createDirectory (Path dir, FileAttribute... attrs) throws IOException { + provider(dir).createDirectory(dir, attrs); + return dir; + } + + public static Path createDirectories (Path dir, FileAttribute... attrs) throws IOException { + final FileSystemProvider provider = provider(dir); + try { + provider.createDirectory(dir, attrs); + return dir; + } + catch (FileAlreadyExistsException e) { + if (Files.isDirectory(dir)) + return dir; + throw e; + } + catch (Exception e) { + // ignore + } + final Path absolute = dir.toAbsolutePath(); + Path parent = absolute.getParent(); + for (;; parent = parent.getParent()) { + if (parent == null) + throw new FileSystemException(dir.toString(), null, "No root directory"); + try { + final BasicFileAttributes basic = provider.readAttributes(parent, BasicFileAttributes.class); + if (!basic.isDirectory()) + throw new FileAlreadyExistsException(dir.toString()); + break; + } + catch (NoSuchFileException e) { + // continue + } + } + Path child = parent; + for (final Path name : parent.relativize(absolute)) + provider.createDirectory(child = child.resolve(name)); + return dir; + } + + public static Path createFile (Path path, FileAttribute... attrs) throws IOException { + try { + newByteChannel(path, EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW), attrs).close(); + } + catch (UnsupportedOperationException e) { + if (attrs.length != 0) + throw e; + newOutputStream(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).close(); + } + return path; + } + + private static Path makeTempPath (Path dir, String prefix, String suffix) { + long n = TEMP_RAND.nextLong(); + if (n == Long.MIN_VALUE) + n = 0; + else if (n < 0) + n *= -1; + final Path name = dir.getFileSystem().getPath(prefix + Long.toString(n) + suffix); + if (name.getParent() != null) + throw new IllegalArgumentException("Invalid prefix or suffix"); + return dir.resolve(name); + } + + public static Path createTempFile (Path dir, String prefix, String suffix, FileAttribute... attrs) throws IOException { + if (prefix == null) + prefix = ""; + if (suffix == null) + suffix = ".tmp"; + for (;;) { + try { + return createFile(makeTempPath(dir, prefix, suffix), attrs); + } + catch (FileAlreadyExistsException e) { + // retry + } + } + } + + public static Path createTempFile (String prefix, String suffix, FileAttribute... attrs) throws IOException { + final FileSystem fs = FileSystems.getDefault(); + if (!(fs instanceof DefaultFileSystem)) + throw new UnsupportedOperationException(); + return createTempFile(((DefaultFileSystem)fs).getCacheDirectory(), prefix, suffix, attrs); + } + + public static Path createTempDirectory (Path dir, String prefix, FileAttribute... attrs) throws IOException { + if (prefix == null) + prefix = ""; + for (;;) { + try { + return Files.createDirectory(makeTempPath(dir, prefix, ""), attrs); + } + catch (FileAlreadyExistsException e) { + // retry + } + } + } + + public static Path createTempDirectory (String prefix, FileAttribute... attrs) throws IOException { + final FileSystem fs = FileSystems.getDefault(); + if (!(fs instanceof DefaultFileSystem)) + throw new UnsupportedOperationException(); + return createTempDirectory(((DefaultFileSystem)fs).getCacheDirectory(), prefix, attrs); + + } + + public static byte[] readAllBytes (Path path) throws IOException { + final InputStream in = newInputStream(path); + try { + final long size = size(path); + if (size > Integer.MAX_VALUE) + throw new OutOfMemoryError("Array size exceeded"); + return Utils.readAllBytes(in, (int)size); + } + finally { + in.close(); + } + } + + public static Path write (Path path, byte[] bytes, OpenOption... options) throws IOException { + final OutputStream out = newOutputStream(path, options); + try { + out.write(bytes); + } + finally { + out.close(); + } + return path; + } + + /** + * Does not delete target upon failure. + */ + public static long copy (InputStream in, Path target, CopyOption... options) throws IOException { + if (in == null) + throw new NullPointerException("in"); + for (final CopyOption option : options) { + if (StandardCopyOption.REPLACE_EXISTING == option) { + deleteIfExists(target); + break; + } + } + final OutputStream out = newOutputStream(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); + try { + return Utils.transfer(in, out); + } + finally { + out.close(); + } + } + + public static long copy (Path source, OutputStream out) throws IOException { + final InputStream in = newInputStream(source); + try { + return Utils.transfer(in, out); + } + finally { + out.close(); + } + } + + public static Path copy (Path source, Path target, CopyOption... options) throws IOException { + return transfer(source, target, false, options); + } + + public static Path move (Path source, Path target, CopyOption... options) throws IOException { + return transfer(source, target, true, options); + } + + private static Path transfer (Path source, Path target, boolean move, CopyOption[] options) throws IOException { + final FileSystemProvider provider = provider(source); + if (provider == provider(target)) { + if (move) + provider.move(source, target, options); + else + provider.copy(source, target, options); + return target; + } + boolean replaceExisting = false; + boolean copyAttributes = false; + LinkOption[] linkOptions = Utils.EMPTY_LINK_OPTION_ARRAY; + for (final CopyOption option : options) { + if (StandardCopyOption.REPLACE_EXISTING == option) + replaceExisting = true; + else if (StandardCopyOption.COPY_ATTRIBUTES == option) + copyAttributes = true; + else if (LinkOption.NOFOLLOW_LINKS == option) + linkOptions = LINK_NOFOLLOW_LINKS; + else if (StandardCopyOption.ATOMIC_MOVE == option && move) + throw new AtomicMoveNotSupportedException(source.toString(), target.toString(), "Different providers"); + } + if (replaceExisting) + deleteIfExists(target); + if (isDirectory(source)) + createDirectory(target); + else { + final InputStream in = newInputStream(source); + try { + final OutputStream out = newOutputStream(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); + try { + Utils.transfer(in, out); + } + finally { + out.close(); + } + } + finally { + in.close(); + } + } + try { + if (copyAttributes) { + for (final Map.Entry attr : readAttributes(source, COPIED_ATTRIBUTES, linkOptions).entrySet()) { + try { + setAttribute(target, attr.getKey(), attr.getValue()); + } + catch (UnsupportedOperationException e) { + // suppressed + } + } + } + if (move) + delete(source); + } + catch (IOException e) { + deleteQuietly(target); + throw e; + } + catch (RuntimeException e) { + deleteQuietly(target); + throw e; + } + return target; + } + + public static SeekableByteChannel newByteChannel(Path path, OpenOption... options) throws IOException { + return provider(path).newByteChannel(path, new SearchSet(options)); + } + + public static SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) throws IOException { + return provider(path).newByteChannel(path, options, attrs); + } + + public static InputStream newInputStream (Path path, OpenOption...options) throws IOException { + return provider(path).newInputStream(path); + } + + public static OutputStream newOutputStream (Path path, OpenOption...options) throws IOException { + return provider(path).newOutputStream(path, options); + } + + public static BufferedReader newBufferedReader (Path path) throws IOException { + return newBufferedReader(path, Utils.UTF_8); + } + + public static BufferedReader newBufferedReader (Path path, Charset charset) throws IOException { + return new BufferedReader(new InputStreamReader(newInputStream(path), charset)); + } + + public static BufferedWriter newBufferedWriter (Path path, OpenOption... options) throws IOException { + return newBufferedWriter(path, Utils.UTF_8, options); + } + + public static BufferedWriter newBufferedWriter (Path path, Charset charset, OpenOption... options) throws IOException { + return new BufferedWriter(new OutputStreamWriter(newOutputStream(path, options), charset)); + } + + public static boolean exists (Path path, LinkOption... options) { + try { + readAttributes(path, BasicFileAttributes.class, options); + return true; + } + catch (IOException e) { + return false; + } + } + + public static boolean notExists (Path path, LinkOption... options) { + try { + readAttributes(path, BasicFileAttributes.class, options); + return false; + } + catch (NoSuchFileException e) { + return true; + } + catch (IOException e) { + return false; + } + } + + public static boolean isSameFile (Path path1, Path path2) throws IOException { + final FileSystem fs = path1.getFileSystem(); + return fs.equals(path2.getFileSystem()) + && fs.provider().isSameFile(path1, path2); + } + + public static boolean isHidden (Path path) throws IOException { + return provider(path).isHidden(path); + } + + public static boolean isDirectory (Path path, LinkOption... options) { + try { + return readAttributes(path, BasicFileAttributes.class, options).isDirectory(); + } + catch (IOException e) { + return false; + } + } + + public static boolean isRegularFile (Path path, LinkOption... options) { + try { + return readAttributes(path, BasicFileAttributes.class, options).isRegularFile(); + } + catch (IOException e) { + return false; + } + } + + public static boolean isSymbolicLink (Path path) { + try { + readSymbolicLink(path); + return true; + } + catch (IOException e) { + return false; + } + } + + public static long size (Path path) throws IOException { + return readAttributes(path, BasicFileAttributes.class).size(); + } + + public static FileTime getLastModifiedTime (Path path, LinkOption... options) throws IOException { + return readAttributes(path, BasicFileAttributes.class, options).lastModifiedTime(); + } + + public static Path setLastModifiedTime (Path path, FileTime time) throws IOException { + return setAttribute(path, BasicFileAttribute.lastModifiedTime.toString(), time); + } + + public static Path readSymbolicLink (Path link) throws IOException { + return provider(link).readSymbolicLink(link); + } + + public static A readAttributes (Path path, Class type, LinkOption... options) throws IOException { + return provider(path).readAttributes(path, type, options); + } + + public static Map readAttributes (Path path, String attributes, LinkOption... options) throws IOException { + return provider(path).readAttributes(path, attributes, options); + } + + public static Path setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException { + provider(path).setAttribute(path, attribute, value, options); + return path; + } + + public static V getFileAttributeView (Path path, Class type, LinkOption... options) { + return provider(path).getFileAttributeView(path, type, options); + } + + public static String probeContentType (Path path) throws IOException { + for (final FileTypeDetector detector : InstalledFileTypeDetectorsHolder.detectors) { + final String type = detector.probeContentType(path); + if (type != null) + return type; + } + return null; + } + + public static DirectoryStream newDirectoryStream (Path dir) throws IOException{ + return newDirectoryStream(dir, Utils.ACCEPT_ALL_FILTER); + } + + public static DirectoryStream newDirectoryStream (Path dir, String glob) throws IOException { + if ("*".equals(glob)) + return newDirectoryStream(dir); + final PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:"+glob); + //noinspection RedundantThrows + return newDirectoryStream(dir, new DirectoryStream.Filter() { + @Override + public boolean accept (Path entry) throws IOException { + return matcher.matches(entry.getFileName()); + } + }); + } + + public static DirectoryStream newDirectoryStream (Path dir, DirectoryStream.Filter filter) throws IOException{ + return provider(dir).newDirectoryStream(dir, filter); + } + + public static Path walkFileTree (Path start, FileVisitor visitor) throws IOException { + return walkFileTree(start, VISIT_EMPTY, Integer.MAX_VALUE, visitor); + } + + @SuppressWarnings("ConstantConditions") + public static Path walkFileTree (Path start, Set options, int maxDepth, FileVisitor visitor) throws IOException { + final boolean followLinks = options.contains(FileVisitOption.FOLLOW_LINKS); + final LinkOption[] linkOptions = followLinks ? Utils.EMPTY_LINK_OPTION_ARRAY : LINK_NOFOLLOW_LINKS; + WalkDirectory dir = null; + try { + Path path = start; + FileVisitResult result; + int depth = 0; + walk: do { + + // iterate + while (dir != null) { + IOException cause = null; + try { + if (dir.iterator.hasNext()) { + path = dir.iterator.next(); + break; + } + } + catch (DirectoryIteratorException e) { + cause = e.getCause(); + } + if (FileVisitResult.TERMINATE == visitor.postVisitDirectory(dir.path, cause) || --depth <= 0) + break walk; + Utils.closeQuietly(dir.stream); + dir = dir.parent; + } + + // descend + DirectoryStream stream = null; + BasicFileAttributes attrs = null; + try { + attrs = readAttributes(path, BasicFileAttributes.class, linkOptions); + if (attrs.isDirectory() && depth < maxDepth) { + if (followLinks) { + // check for recursion + for (WalkDirectory ancestor = dir; ancestor != null; ancestor = ancestor.parent) { + if (ancestor.isSameFile(path, attrs.fileKey())) + throw new FileSystemLoopException(path.toString()); + } + } + stream = Files.newDirectoryStream(path); + } + } + catch (NotDirectoryException e) { + // visitFile below + } + catch (IOException e) { + result = visitor.visitFileFailed(path, e); + if (FileVisitResult.SKIP_SIBLINGS == result && dir != null) + dir.iterator = Utils.emptyIterator(); + continue; + } + if (stream != null) { + result = visitor.preVisitDirectory(path, attrs); + if (FileVisitResult.CONTINUE == result) { + dir = new WalkDirectory(dir, path, attrs.fileKey(), stream); + ++depth; + } + else + Utils.closeQuietly(stream); + } + else { + result = visitor.visitFile(path, attrs); + if (FileVisitResult.SKIP_SIBLINGS == result && dir != null) + dir.iterator = Utils.emptyIterator(); + } + } while (FileVisitResult.TERMINATE != result && depth > 0); + } + finally { + for (; dir != null; dir = dir.parent) + Utils.closeQuietly(dir.stream); + } + return start; + } + + + public static File[] walk(@Nullable Path path) { + throw new NotImplementedException("我懒得写"); + } + + + private static final class WalkDirectory { + + public final WalkDirectory parent; + public final Path path; + public final Object key; + public final DirectoryStream stream; + public Iterator iterator; + + public WalkDirectory (WalkDirectory parent, Path path, Object key, DirectoryStream stream) { + this.parent = parent; + this.path = path; + this.key = key; + this.stream = stream; + this.iterator = stream.iterator(); + } + + public boolean isSameFile (Path path, Object key) { + if (key != null && this.key != null) + return key.equals(this.key); + try { + return Files.isSameFile(path, this.path); + } + catch (IOException e) { + return false; + } + } + + } // class WalkDirectory + + + private static FileSystemProvider provider (Path path) { + return path.getFileSystem().provider(); + } + + + private static final class InstalledFileTypeDetectorsHolder { + + private static final List detectors = loadInstalledProviders(); + + private static List loadInstalledProviders () { + final List detectors = new ArrayList(); + for (final FileTypeDetector detector : ServiceLoader.load(FileTypeDetector.class, FileTypeDetector.class.getClassLoader())) + detectors.add(detector); + //noinspection RedundantThrows + detectors.add(new FileTypeDetector() { + private final boolean apacheHarmony = isApacheHarmony(); + @Override + public String probeContentType (Path path) throws IOException { + // TODO: Use URLConnection.guessContentTypeFromStream() + String filename = path.toString(); + // BUG: https://code.google.com/p/android/issues/detail?id=162883 + // https://android.googlesource.com/platform/libcore/+/marshmallow-release/luni/src/main/java/java/net/DefaultFileNameMap.java + if (apacheHarmony) + filename = filename.replace('#', '_'); + return URLConnection.guessContentTypeFromName(filename); + } + }); + return detectors; + } + + private static boolean isApacheHarmony () { + try { + Class.forName("libcore.net.MimeUtils", false, InstalledFileTypeDetectorsHolder.class.getClassLoader()); + return true; + } + catch (ClassNotFoundException e) { + return false; + } + } + + } // class InstalledFileTypeDetectorsHolder + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/InvalidPathException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/InvalidPathException.java new file mode 100644 index 0000000000..4bd781ea1f --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/InvalidPathException.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class InvalidPathException extends IllegalArgumentException { + + private final String input; + private final int index; + + public InvalidPathException (String input, String reason) { + this(input, reason, -1); + } + + public InvalidPathException (String input, String reason, int index) { + super(reason); + this.input = input; + this.index = index; + } + + public String getInput () { + return input; + } + + public String getReason () { + return super.getMessage(); + } + + public int getIndex () { + return index; + } + + @Override + public String getMessage () { + final StringBuilder sb = new StringBuilder().append(getReason()); + if (index >= 0) + sb.append(" at index ").append(index); + return sb.append(": ").append(input).toString(); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/LinkOption.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/LinkOption.java new file mode 100644 index 0000000000..4c7a0659ae --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/LinkOption.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public enum LinkOption implements OpenOption, CopyOption { + /** Do not follow symbolic links. */ + NOFOLLOW_LINKS, +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/NoSuchFileException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/NoSuchFileException.java new file mode 100644 index 0000000000..adeb9bd4e0 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/NoSuchFileException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class NoSuchFileException extends FileSystemException { + + public NoSuchFileException (String file) { + super(file); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/NotDirectoryException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/NotDirectoryException.java new file mode 100644 index 0000000000..a8567ef0c1 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/NotDirectoryException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class NotDirectoryException extends FileSystemException { + + public NotDirectoryException (String file) { + super(file); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/NotLinkException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/NotLinkException.java new file mode 100644 index 0000000000..d223dac2bb --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/NotLinkException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class NotLinkException extends FileSystemException { + + public NotLinkException (String file) { + super(file); + } + + public NotLinkException (String file, String otherFile, String reason) { + super(file, otherFile, reason); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/OpenOption.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/OpenOption.java new file mode 100644 index 0000000000..3fc183218c --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/OpenOption.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public interface OpenOption {} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/Path.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Path.java new file mode 100644 index 0000000000..b1a7073b04 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Path.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.io.File; +import java.io.IOException; +import java.net.URI; + +public interface Path extends Comparable, Iterable, Watchable { + public FileSystem getFileSystem (); + + public boolean isAbsolute (); + public Path getRoot (); + public Path getParent (); + public Path getFileName (); + public int getNameCount (); + public Path getName (int index); + public Path subpath (int beginIndex, int endIndex); + public boolean startsWith (Path other); + public boolean startsWith (String other); + public boolean endsWith (Path other); + public boolean endsWith (String other); + + public Path normalize (); + public Path relativize (Path other); + public Path resolve (Path other); + public Path resolve (String other); + public Path resolveSibling (Path other); + public Path resolveSibling (String other); + + public Path toAbsolutePath (); + public Path toRealPath (LinkOption... options) throws IOException; + public File toFile (); + public URI toUri (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/PathMatcher.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/PathMatcher.java new file mode 100644 index 0000000000..6d2d16c490 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/PathMatcher.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public interface PathMatcher { + public boolean matches (Path path); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/Paths.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Paths.java new file mode 100644 index 0000000000..d349058485 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Paths.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import com.llamalab.safs.spi.FileSystemProvider; + +import java.io.File; +import java.net.URI; + +public final class Paths { + + private Paths () {} + + public static Path get (String first, String... more) { + return FileSystems.getDefault().getPath(first, more); + } + + public static Path get (URI uri) { + final String scheme = uri.getScheme(); + if (scheme == null) + throw new IllegalArgumentException("No scheme"); + for (final FileSystemProvider provider: FileSystemProvider.installedProviders()) { + if (scheme.equalsIgnoreCase(provider.getScheme())) + return provider.getPath(uri); + } + throw new FileSystemNotFoundException(scheme); + } + + /** + * Non-standard. Since we can't add toPath to {@link java.io.File}. + */ + public static Path get (File file) { + return get(file.toString()); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/ProviderMismatchException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ProviderMismatchException.java new file mode 100644 index 0000000000..5f5eed7d16 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ProviderMismatchException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class ProviderMismatchException extends IllegalArgumentException { + + public ProviderMismatchException () {} + + public ProviderMismatchException (String message) { + super(message); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/ProviderNotFoundException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ProviderNotFoundException.java new file mode 100644 index 0000000000..6315ce1ab8 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ProviderNotFoundException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class ProviderNotFoundException extends RuntimeException { + + public ProviderNotFoundException () {} + + public ProviderNotFoundException (String message) { + super(message); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/ReadOnlyFileSystemException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ReadOnlyFileSystemException.java new file mode 100644 index 0000000000..b61d4e8e3a --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/ReadOnlyFileSystemException.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +@SuppressWarnings("serial") +public class ReadOnlyFileSystemException extends UnsupportedOperationException { +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/SimpleFileVisitor.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/SimpleFileVisitor.java new file mode 100644 index 0000000000..9d749fa934 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/SimpleFileVisitor.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import com.llamalab.safs.attribute.BasicFileAttributes; + +import java.io.IOException; + +public class SimpleFileVisitor implements FileVisitor { + + @Override + public FileVisitResult preVisitDirectory (T dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory (T dir, IOException e) throws IOException { + if (e != null) + throw e; + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile (T file, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed (T file, IOException e) throws IOException { + throw e; + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardCopyOption.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardCopyOption.java new file mode 100644 index 0000000000..3187b672ff --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardCopyOption.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public enum StandardCopyOption implements CopyOption { + REPLACE_EXISTING, + COPY_ATTRIBUTES, + ATOMIC_MOVE, +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardOpenOption.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardOpenOption.java new file mode 100644 index 0000000000..04c147f12a --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardOpenOption.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public enum StandardOpenOption implements OpenOption { + READ, + WRITE, + APPEND, + TRUNCATE_EXISTING, + CREATE, + CREATE_NEW, + /** Unsupported. */ + DELETE_ON_CLOSE, + SPARSE, + SYNC, + DSYNC, +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardWatchEventKinds.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardWatchEventKinds.java new file mode 100644 index 0000000000..2adc8994f2 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/StandardWatchEventKinds.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import com.llamalab.safs.internal.WatchEventKind; + +public final class StandardWatchEventKinds { + + public static final WatchEvent.Kind OVERFLOW = new WatchEventKind("OVERFLOW", Object.class); + public static final WatchEvent.Kind ENTRY_CREATE = new WatchEventKind("ENTRY_CREATE", Path.class); + public static final WatchEvent.Kind ENTRY_DELETE = new WatchEventKind("ENTRY_DELETE", Path.class); + public static final WatchEvent.Kind ENTRY_MODIFY = new WatchEventKind("ENTRY_MODIFY", Path.class); + + private StandardWatchEventKinds() {} + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchEvent.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchEvent.java new file mode 100644 index 0000000000..28295667da --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +public interface WatchEvent { + + public static interface Kind { + public String name (); + public Class type (); + } + + public static interface Modifier { + public String name (); + } + + public Kind kind (); + public T context (); + public int count (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchKey.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchKey.java new file mode 100644 index 0000000000..27c1796779 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchKey.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.util.List; + +public interface WatchKey { + public Watchable watchable (); + public boolean isValid (); + public boolean reset (); + public void cancel (); + public List> pollEvents (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchService.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchService.java new file mode 100644 index 0000000000..6f03ed45fa --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/WatchService.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.io.Closeable; +import java.util.concurrent.TimeUnit; + +public interface WatchService extends Closeable { + public WatchKey poll (); + public WatchKey poll (long timeout, TimeUnit unit) throws InterruptedException; + public WatchKey take () throws InterruptedException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/Watchable.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Watchable.java new file mode 100644 index 0000000000..a7c55288d6 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/Watchable.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs; + +import java.io.IOException; + +public interface Watchable { + public WatchKey register (WatchService service, WatchEvent.Kind[] kinds, WatchEvent.Modifier... modifiers) throws IOException; + public WatchKey register (WatchService service, WatchEvent.Kind... kinds) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/AttributeView.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/AttributeView.java new file mode 100644 index 0000000000..09f5012cfa --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/AttributeView.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +public interface AttributeView { + public String name (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/BasicFileAttributeView.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/BasicFileAttributeView.java new file mode 100644 index 0000000000..edb3490b22 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/BasicFileAttributeView.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import java.io.IOException; + +public interface BasicFileAttributeView extends FileAttributeView { + public BasicFileAttributes readAttributes () throws IOException; + public void setTimes (FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/BasicFileAttributes.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/BasicFileAttributes.java new file mode 100644 index 0000000000..b362a79003 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/BasicFileAttributes.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +public interface BasicFileAttributes { + public Object fileKey (); + public boolean isDirectory (); + public boolean isOther (); + public boolean isRegularFile (); + public boolean isSymbolicLink (); + public long size (); + public FileTime creationTime (); + public FileTime lastModifiedTime (); + public FileTime lastAccessTime (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileAttribute.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileAttribute.java new file mode 100644 index 0000000000..3e6e0a3993 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileAttribute.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +public interface FileAttribute { + public String name (); + public T value (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileAttributeView.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileAttributeView.java new file mode 100644 index 0000000000..07295c2d2c --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileAttributeView.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +public interface FileAttributeView extends AttributeView { +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileOwnerAttributeView.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileOwnerAttributeView.java new file mode 100644 index 0000000000..ddf5bed34a --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileOwnerAttributeView.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import java.io.IOException; + +public interface FileOwnerAttributeView extends FileAttributeView { + public UserPrincipal getOwner () throws IOException; + public void setOwner (UserPrincipal owner) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileTime.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileTime.java new file mode 100644 index 0000000000..68ecfeaf5e --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/FileTime.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import com.llamalab.safs.internal.Utils; + +import java.util.concurrent.TimeUnit; + +public final class FileTime implements Comparable { + + private static final FileTime ZERO = new FileTime(0); + + private final long value; + + private FileTime (long value) { + this.value = value; + } + + /** + * YYYY-MM-DDThh:mm:ss[.s+]Z + */ + @Override + public String toString () { + return Utils.formatRfc3339(value); + } + + @Override + public int hashCode () { + return (int)(value ^ (value >>> 32)); + } + + @Override + public boolean equals (Object obj) { + if (this == obj) + return true; + if (!(obj instanceof FileTime)) + return false; + return value == ((FileTime)obj).value; + } + + @Override + public int compareTo (FileTime other) { + final long lhs = this.value; + final long rhs = other.value; + //noinspection UseCompareMethod + return (lhs < rhs) ? -1 : (lhs == rhs) ? 0 : 1; + } + + public long to (TimeUnit unit) { + return unit.convert(value, TimeUnit.MILLISECONDS); + } + + public long toMillis () { + return to(TimeUnit.MILLISECONDS); + } + + public static FileTime from (long value, TimeUnit unit) { + if (unit == null) + throw new NullPointerException("unit"); + return fromMillis(TimeUnit.MILLISECONDS.convert(value, unit)); + } + + public static FileTime fromMillis (long value) { + return (value != 0) ? new FileTime(value) : ZERO; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/GroupPrincipal.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/GroupPrincipal.java new file mode 100644 index 0000000000..1d8446c265 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/GroupPrincipal.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +public interface GroupPrincipal extends UserPrincipal { +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFileAttributeView.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFileAttributeView.java new file mode 100644 index 0000000000..963bfa9362 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFileAttributeView.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import java.io.IOException; +import java.util.Set; + +public interface PosixFileAttributeView extends BasicFileAttributeView, FileOwnerAttributeView { + public void setGroup (GroupPrincipal group) throws IOException; + public void setPermissions (Set perms) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFileAttributes.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFileAttributes.java new file mode 100644 index 0000000000..ab9bf9c60a --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFileAttributes.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import java.util.Set; + +public interface PosixFileAttributes extends BasicFileAttributes { + public GroupPrincipal group (); + public UserPrincipal owner (); + public Set permissions (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFilePermission.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFilePermission.java new file mode 100644 index 0000000000..a3b36aea75 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFilePermission.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +public enum PosixFilePermission { + OWNER_READ, + OWNER_WRITE, + OWNER_EXECUTE, + GROUP_READ, + GROUP_WRITE, + GROUP_EXECUTE, + OTHERS_READ, + OTHERS_WRITE, + OTHERS_EXECUTE +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFilePermissions.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFilePermissions.java new file mode 100644 index 0000000000..95b215e7df --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/PosixFilePermissions.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import com.llamalab.safs.internal.SearchSet; + +import java.util.EnumSet; +import java.util.Set; + +public final class PosixFilePermissions { + + private PosixFilePermissions () {} + + public static String toString (Set perms) { + final StringBuilder sb = new StringBuilder(9); + append(sb, + perms.contains(PosixFilePermission.OWNER_READ), + perms.contains(PosixFilePermission.OWNER_WRITE), + perms.contains(PosixFilePermission.OWNER_EXECUTE)); + append(sb, + perms.contains(PosixFilePermission.GROUP_READ), + perms.contains(PosixFilePermission.GROUP_WRITE), + perms.contains(PosixFilePermission.GROUP_EXECUTE)); + append(sb, + perms.contains(PosixFilePermission.OTHERS_READ), + perms.contains(PosixFilePermission.OTHERS_WRITE), + perms.contains(PosixFilePermission.OTHERS_EXECUTE)); + return sb.toString(); + } + + private static void append (StringBuilder sb, boolean r, boolean w, boolean x) { + sb.append(r ? 'r' : '-').append(w ? 'w' : '-').append(x ? 'x' : '-'); + } + + public static Set fromString (String mode) { + if (mode.length() != 9) + throw new IllegalArgumentException(); + final Set set = EnumSet.noneOf(PosixFilePermission.class); + add(set, mode, 0, 'r', PosixFilePermission.OWNER_READ); + add(set, mode, 1, 'w', PosixFilePermission.OWNER_WRITE); + add(set, mode, 2, 'x', PosixFilePermission.OWNER_EXECUTE); + + add(set, mode, 3, 'r', PosixFilePermission.GROUP_READ); + add(set, mode, 4, 'w', PosixFilePermission.GROUP_WRITE); + add(set, mode, 5, 'x', PosixFilePermission.GROUP_EXECUTE); + + add(set, mode, 6, 'r', PosixFilePermission.OTHERS_READ); + add(set, mode, 7, 'w', PosixFilePermission.OTHERS_WRITE); + add(set, mode, 8, 'x', PosixFilePermission.OTHERS_EXECUTE); + return set; + } + + private static void add (Set set, String mode, int index, char flag, PosixFilePermission perm) { + final char c = mode.charAt(index); + if (flag == c) + set.add(perm); + else if ('-' != c) + throw new IllegalArgumentException(); + } + + public static FileAttribute> asFileAttribute (Set perms) { + //noinspection ToArrayCallWithZeroLengthArrayArgument + final Set value = new SearchSet(perms.toArray(new PosixFilePermission[perms.size()])); + return new FileAttribute>() { + + @Override + public String name() { + return "posix:permissions"; + } + + @Override + public Set value () { + return value; + } + }; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipal.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipal.java new file mode 100644 index 0000000000..f8c888d105 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipal.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import java.security.Principal; + +public interface UserPrincipal extends Principal { +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipalLookupService.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipalLookupService.java new file mode 100644 index 0000000000..1240c7b33f --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipalLookupService.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import java.io.IOException; + +public abstract class UserPrincipalLookupService { + + protected UserPrincipalLookupService () {} + + public abstract UserPrincipal lookupPrincipalByName (String name) throws IOException; + public abstract GroupPrincipal lookupPrincipalByGroupName (String group) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipalNotFoundException.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipalNotFoundException.java new file mode 100644 index 0000000000..215d1d2948 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/attribute/UserPrincipalNotFoundException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.attribute; + +import java.io.IOException; + +public class UserPrincipalNotFoundException extends IOException { + + private final String name; + + public UserPrincipalNotFoundException (String name) { + this.name = name; + } + + public String getName (){ + return name; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/channels/SeekableByteChannel.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/channels/SeekableByteChannel.java new file mode 100644 index 0000000000..7000795caa --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/channels/SeekableByteChannel.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.channels; + +import java.io.IOException; +import java.nio.channels.ByteChannel; + +public interface SeekableByteChannel extends ByteChannel { + public long position () throws IOException; + public SeekableByteChannel position (long newPosition) throws IOException; + public long size () throws IOException; + public SeekableByteChannel truncate (long size) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractDirectoryStream.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractDirectoryStream.java new file mode 100644 index 0000000000..932461f059 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractDirectoryStream.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.DirectoryIteratorException; +import com.llamalab.safs.DirectoryStream; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public abstract class AbstractDirectoryStream implements DirectoryStream, Iterator { + + private T next; + private boolean started; + private boolean closed; + + /** + * Make sure to call super.close(); + */ + @Override + public final void close () throws IOException { + if (!closed) { + closed = true; + implCloseStream(); + } + } + + protected void implCloseStream () throws IOException {} + + @SuppressWarnings("NullableProblems") + @Override + public final Iterator iterator () { + if (started) + throw new IllegalStateException(); + started = true; + return this; + } + + @Override + public final boolean hasNext () { + if (next != null) + return true; + if (closed) + return false; + try { + return (next = advance()) != null; + } + catch (IOException e) { + throw new DirectoryIteratorException(e); + } + } + + @Override + public final T next () { + if (closed || !hasNext()) + throw new NoSuchElementException(); + final T result = next; + next = null; + return result; + } + + @Override + public final void remove () { + throw new UnsupportedOperationException(); + } + + protected abstract T advance () throws IOException; +} \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractFileSystemProvider.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractFileSystemProvider.java new file mode 100644 index 0000000000..944383fbee --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractFileSystemProvider.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.LinkOption; +import com.llamalab.safs.NoSuchFileException; +import com.llamalab.safs.OpenOption; +import com.llamalab.safs.Path; +import com.llamalab.safs.ProviderMismatchException; +import com.llamalab.safs.StandardOpenOption; +import com.llamalab.safs.attribute.BasicFileAttributeView; +import com.llamalab.safs.attribute.BasicFileAttributes; +import com.llamalab.safs.attribute.FileAttribute; +import com.llamalab.safs.attribute.FileAttributeView; +import com.llamalab.safs.attribute.FileTime; +import com.llamalab.safs.spi.FileSystemProvider; +import com.llamalab.safs.unix.UnixPath; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Only support {@link UnixPath}. + */ +public abstract class AbstractFileSystemProvider extends FileSystemProvider { + + protected static final Set DEFAULT_NEW_INPUT_STREAM_OPTIONS = EnumSet.of(StandardOpenOption.READ); + protected static final Set DEFAULT_NEW_OUTPUT_STREAM_OPTIONS = EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + protected abstract Class getPathType (); + + protected void checkPath (Path path) { + if (!getPathType().isInstance(path)) + throw (path == null) ? new NullPointerException() : new ProviderMismatchException(); + if (path.getFileSystem().provider() != this) + throw new ProviderMismatchException(); + } + + protected void checkUri (URI uri) { + if (!getScheme().equalsIgnoreCase(uri.getScheme())) + throw new ProviderMismatchException(); + } + + protected IOException toProperException (IOException ioe, String file, String otherFile) { + return (ioe instanceof FileNotFoundException) ? new NoSuchFileException(file) : ioe; + } + + @Override + public Map readAttributes (Path path, String attributes, LinkOption... options) throws IOException { + final Map map = new HashMap(); + BasicFileAttributes basic = null; + for (final BasicFileAttribute attribute : BasicFileAttribute.parse(attributes)) { + if (basic == null) + basic = readAttributes(path, BasicFileAttributes.class, options); // read once + map.put(attribute.toString(), attribute.valueOf(basic)); + } + return map; + } + + @Override + public void setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException { + String viewName = BasicFileAttribute.VIEW_NAME; + final int colon = attribute.indexOf(':'); + if (colon != -1) { + viewName = attribute.substring(0, colon); + attribute = attribute.substring(colon + 1); + } + final FileAttribute attr = newFileAttribute(viewName, attribute, value); + setAttributes(path, Collections.singleton(attr), options); + } + + protected FileAttribute newFileAttribute (String viewName, String attribute, Object value) { + if (BasicFileAttribute.VIEW_NAME.equals(viewName)) + return BasicFileAttribute.valueOf(attribute).newFileAttribute(value); + throw new UnsupportedOperationException("Attribute: "+viewName+":"+attribute); + } + + protected abstract void setAttributes (Path path, Set> attrs, LinkOption... options) throws IOException; + + @SuppressWarnings("unchecked") + public V getFileAttributeView (final Path path, Class type, final LinkOption... options) { + checkPath(path); + if (BasicFileAttributeView.class == type) + return (V)new BasicFileAttributeViewImpl(path, options); + return null; + } + + + protected class BasicFileAttributeViewImpl implements BasicFileAttributeView { + + protected final Path path; + protected final LinkOption[] options; + + public BasicFileAttributeViewImpl (Path path, LinkOption[] options) { + this.path = path; + this.options = options; + } + + @Override + public String name () { + return BasicFileAttribute.VIEW_NAME; + } + + @Override + public BasicFileAttributes readAttributes () throws IOException { + return AbstractFileSystemProvider.this.readAttributes(path, BasicFileAttributes.class, options); + } + + @Override + public void setTimes (FileTime lastModifiedTime, FileTime lastAccessTime, FileTime creationTime) throws IOException { + final Set> attrs = new HashSet>(); + if (lastModifiedTime != null) + attrs.add(BasicFileAttribute.lastModifiedTime.newFileAttribute(lastModifiedTime)); + if (lastAccessTime != null) + attrs.add(BasicFileAttribute.lastAccessTime.newFileAttribute(lastAccessTime)); + if (creationTime != null) + attrs.add(BasicFileAttribute.creationTime.newFileAttribute(creationTime)); + AbstractFileSystemProvider.this.setAttributes(path, attrs, options); + } + + } // class BasicFileAttributeViewImpl + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractWatchKey.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractWatchKey.java new file mode 100644 index 0000000000..44a55c0195 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractWatchKey.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.StandardWatchEventKinds; +import com.llamalab.safs.WatchEvent; +import com.llamalab.safs.WatchKey; +import com.llamalab.safs.Watchable; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractWatchKey implements WatchKey { + + private enum State { + READY, + SIGNALLED, + } + private final Object eventLock = new Object(); + private final AbstractWatchService service; + private final Watchable watchable; + private final int overflowLimit; + private List> events = new ArrayList>(); + private State state = State.READY; + + public AbstractWatchKey (AbstractWatchService service, Watchable watchable, int overflowLimit) { + this.service = service; + this.watchable = watchable; + this.overflowLimit = overflowLimit; + } + + public final AbstractWatchService service () { + return service; + } + + @Override + public final Watchable watchable () { + return watchable; + } + + @Override + public boolean isValid () { + return service.isOpen(); + } + + @Override + public final boolean reset () { + synchronized (eventLock) { + if (!isValid()) + return false; + if (State.SIGNALLED == state) { + if (events.isEmpty()) + state = State.READY; + else + service.offer(this); + } + return true; + } + } + + @Override + public final List> pollEvents () { + synchronized (eventLock) { + final List> result = events; + events = new ArrayList>(); + return result; + } + } + + protected final void signalEvent (WatchEvent.Kind kind, T context) { + synchronized (eventLock) { + final int size = events.size(); + if (size > 0) { + final Event event = (Event)events.get(size - 1); + if ( StandardWatchEventKinds.OVERFLOW == event.kind + || (event.kind == kind && Utils.equals(event.context, context))) { + ++event.count; + return; + } + } + if (size < overflowLimit) + events.add(new Event(kind, context)); + else + events.add(new Event(StandardWatchEventKinds.OVERFLOW, null)); + if (State.READY == state) { + state = State.SIGNALLED; + service.offer(this); + } + } + } + + private static final class Event implements WatchEvent { + + private final WatchEvent.Kind kind; + private final T context; + private int count = 1; + + public Event (WatchEvent.Kind kind, T context) { + this.kind = kind; + this.context = context; + } + + @Override + public Kind kind () { + return kind; + } + + @Override + public T context () { + return context; + } + + @Override + public int count () { + return count; + } + + @Override + public String toString () { + return super.toString()+"[kind="+kind+", context="+context+", count="+count+"]"; + } + + } // class Event +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractWatchService.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractWatchService.java new file mode 100644 index 0000000000..fded88eedb --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AbstractWatchService.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.ClosedWatchServiceException; +import com.llamalab.safs.WatchEvent; +import com.llamalab.safs.WatchKey; +import com.llamalab.safs.WatchService; +import com.llamalab.safs.Watchable; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class AbstractWatchService implements WatchService { + + private final WatchKey CLOSE_KEY = new WatchKey() { + + @Override + public Watchable watchable () { + return null; + } + + @Override + public boolean isValid () { + return true; + } + + @Override + public boolean reset () { + return true; + } + + @Override + public void cancel () { + } + + @Override + public List> pollEvents () { + return null; + } + }; + private final LinkedBlockingDeque pendingKeys = new LinkedBlockingDeque(); + private final AtomicBoolean closed = new AtomicBoolean(); + + final void offer (WatchKey key) { + pendingKeys.offer(key); + } + + @Override + public final WatchKey poll () { + checkOpen(); + return checkKey(pendingKeys.poll()); + } + + @Override + public final WatchKey poll (long timeout, TimeUnit unit) throws InterruptedException { + checkOpen(); + return checkKey(pendingKeys.poll(timeout, unit)); + } + + @Override + public final WatchKey take () throws InterruptedException { + checkOpen(); + return checkKey(pendingKeys.take()); + } + + @Override + public final void close () throws IOException { + if (closed.compareAndSet(false, true)) { + try { + implCloseService(); + } + finally { + pendingKeys.clear(); + pendingKeys.offer(CLOSE_KEY); + } + } + } + + protected abstract void implCloseService () throws IOException; + + public final boolean isOpen () { + return !closed.get(); + } + + private void checkOpen () { + if (closed.get()) + throw new ClosedWatchServiceException(); + } + + private WatchKey checkKey (WatchKey key) { + if (CLOSE_KEY == key) + pendingKeys.offer(key); + checkOpen(); + return key; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AttributeParser.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AttributeParser.java new file mode 100644 index 0000000000..738cec2a8d --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/AttributeParser.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import java.util.EnumSet; +import java.util.Iterator; + +final class AttributeParser> implements Iterable { + + private final Class enumType; + private final String attributes; + private final String viewName; + private final boolean isDefault; + + public AttributeParser (Class enumType, String attributes, String viewName, boolean isDefault) { + this.enumType = enumType; + this.attributes = attributes; + this.viewName = viewName; + this.isDefault = isDefault; + } + + @SuppressWarnings("NullableProblems") + @Override + public Iterator iterator () { + final int length = attributes.length(); + int start = 0, index = 0; + boolean prefixed = false; + loop: while (index < length) { + switch (attributes.charAt(index)) { + case ':': + if (prefixed) + throw new IllegalArgumentException(); + if (index != viewName.length() || !attributes.startsWith(viewName)) + return Utils.emptyIterator(); + start = ++index; + prefixed = true; + break; + case '*': + if (index != start || ++index != length) + throw new IllegalArgumentException(); + if (!prefixed && !isDefault) + return Utils.emptyIterator(); + return EnumSet.allOf(enumType).iterator(); + case ',': + break loop; + default: + ++index; + } + } + if (!prefixed && !isDefault) + return Utils.emptyIterator(); + return new CommaSeparatedIterator(start, index); + } + + private final class CommaSeparatedIterator implements Iterator { + + private int start; + private int index; + + public CommaSeparatedIterator (int start, int index) { + this.start = start; + this.index = index; + } + + @Override + public boolean hasNext () { + return start < index; + } + + @Override + public E next () { + final E element = Enum.valueOf(enumType, attributes.substring(start, index)); + index = attributes.indexOf(',', start = index + 1); + if (index == -1) + index = attributes.length(); + return element; + } + + @Override + public void remove () { + throw new UnsupportedOperationException(); + } + + } // class CommaSeparatedIterator +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/BasicFileAttribute.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/BasicFileAttribute.java new file mode 100644 index 0000000000..d5e416e4ec --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/BasicFileAttribute.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.attribute.BasicFileAttributes; +import com.llamalab.safs.attribute.FileAttribute; + +public enum BasicFileAttribute { + fileKey { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.fileKey(); + } + }, + isDirectory { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.isDirectory(); + } + }, + isRegularFile { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.isRegularFile(); + } + }, + isSymbolicLink { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.isSymbolicLink(); + } + }, + isOther { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.isOther(); + } + }, + size { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.size(); + } + }, + creationTime { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.creationTime(); + } + }, + lastModifiedTime { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.lastModifiedTime(); + } + }, + lastAccessTime { + @Override + public Object valueOf (BasicFileAttributes attrs) { + return attrs.lastAccessTime(); + } + }; + + public static final String VIEW_NAME = "basic"; + + public abstract Object valueOf (BasicFileAttributes attrs); + + public FileAttribute newFileAttribute (Object value) { + return new BasicFileAttributeValue(this, value); + } + + public static Iterable parse (String attributes) { + return new AttributeParser(BasicFileAttribute.class, attributes, VIEW_NAME, true); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/BasicFileAttributeValue.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/BasicFileAttributeValue.java new file mode 100644 index 0000000000..3c56b1542e --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/BasicFileAttributeValue.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.attribute.FileAttribute; + +public final class BasicFileAttributeValue implements FileAttribute { + + private final BasicFileAttribute type; + private final Object value; + + public BasicFileAttributeValue (BasicFileAttribute type, Object value) { + if (type == null || value == null) + throw new NullPointerException(); + this.type = type; + this.value = value; + } + + @Override + public String name () { + return BasicFileAttribute.VIEW_NAME + ":" + type; + } + + @Override + public Object value () { + return value; + } + + public BasicFileAttribute type () { + return type; + } + + @Override + public int hashCode () { + return type.hashCode(); + } + + @Override + public boolean equals (Object other) { + return other instanceof BasicFileAttributeValue + && type == ((BasicFileAttributeValue)other).type; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/CompleteBasicFileAttributes.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/CompleteBasicFileAttributes.java new file mode 100644 index 0000000000..d2f42a6f9a --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/CompleteBasicFileAttributes.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.attribute.FileTime; + +public class CompleteBasicFileAttributes extends PartialBasicFileAttributes { + + private final Object fileKey; + private final FileTime creationTime; + private final FileTime lastModifiedTime; + private final FileTime lastAccessTime; + + public CompleteBasicFileAttributes (Object fileKey, FileType type, long size, FileTime creationTime, FileTime lastModifiedTime, FileTime lastAccessTime) { + super(type, size); + this.fileKey = fileKey; + this.creationTime = creationTime; + this.lastModifiedTime = lastModifiedTime; + this.lastAccessTime = lastAccessTime; + } + + @Override + public final Object fileKey () { + return fileKey; + } + + @Override + public final FileTime creationTime () { + return creationTime; + } + + @Override + public final FileTime lastModifiedTime () { + return lastModifiedTime; + } + + @Override + public final FileTime lastAccessTime () { + return lastAccessTime; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/DefaultFileSystem.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/DefaultFileSystem.java new file mode 100644 index 0000000000..9f653647cc --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/DefaultFileSystem.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.Path; + +public interface DefaultFileSystem { + public Path getCacheDirectory (); +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/FileType.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/FileType.java new file mode 100644 index 0000000000..24b7fe9f21 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/FileType.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +public enum FileType { + DIRECTORY, + REGULAR_FILE, + SYMBOLIC_LINK, + OTHER, +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/PartialBasicFileAttributes.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/PartialBasicFileAttributes.java new file mode 100644 index 0000000000..2ba3240f35 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/PartialBasicFileAttributes.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.attribute.BasicFileAttributes; + +public abstract class PartialBasicFileAttributes implements BasicFileAttributes { + + private final FileType type; + private final long size; + + protected PartialBasicFileAttributes (FileType type, long size) { + this.type = type; + this.size = size; + } + + @Override + public final boolean isDirectory () { + return FileType.DIRECTORY == type; + } + + @Override + public final boolean isRegularFile () { + return FileType.REGULAR_FILE == type; + } + + @Override + public final boolean isSymbolicLink () { + return FileType.SYMBOLIC_LINK == type; + } + + @Override + public final boolean isOther () { + return FileType.OTHER == type; + } + + @Override + public final long size () { + return size; + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/PathDescender.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/PathDescender.java new file mode 100644 index 0000000000..c8a8ccdfd7 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/PathDescender.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import java.util.Iterator; + +public class PathDescender implements Iterator { + + public enum Event { + DIRECTORY, + FILE, + MISSING_DIRECTORY, + MISSING_FILE + } + + private final Iterator segments; + private SegmentEntry parent; + private String segment; + private int index; + + public PathDescender (T root, Iterator segments) { + this.parent = root; + this.segments = segments; + } + + @Override + public boolean hasNext () { + return index >= 0 && segments.hasNext(); + } + + @Override + public Event next () { + if (segment != null) + parent = parent.children[index]; + if ((index = parent.binarySearch(segment = segments.next())) >= 0) + return segments.hasNext() ? Event.DIRECTORY : Event.FILE; + else + return segments.hasNext() ? Event.MISSING_DIRECTORY : Event.MISSING_FILE; + } + + @Override + public void remove () { + parent.remove(index); + index = -1; + } + + public void set (T entry) { + entry.segment = segment; + index = parent.put(index, entry); + } + + public String segment () { + return segment; + } + + @SuppressWarnings("unchecked") + public T parent () { + return (T)parent; + } + + @SuppressWarnings("unchecked") + public T entry () { + return (T)parent.children[index]; + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/SearchSet.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/SearchSet.java new file mode 100644 index 0000000000..e88762a50f --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/SearchSet.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Values must MUST implement Comparable. + */ +public class SearchSet extends AbstractSet implements Comparator { + + private final Object[] elements; + + @SafeVarargs + public SearchSet (T... elements) { + this.elements = elements; + Arrays.sort(elements, this); + } + + @SuppressWarnings("NullableProblems") + @Override + public Iterator iterator () { + return new Iterator() { + private int index; + + @Override + public boolean hasNext () { + return index < elements.length; + } + + @SuppressWarnings("unchecked") + @Override + public T next () { + if (index >= elements.length) + throw new NoSuchElementException(); + return (T)elements[index++]; + } + + @Override + public void remove () { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int size () { + return elements.length; + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean addAll (Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear () { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains (Object object) { + return Arrays.binarySearch(elements, object, this) >= 0; + } + + @Override + public boolean remove (Object object) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean retainAll (Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll (Collection collection) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public int compare (Object lhs, Object rhs) { + if (lhs == rhs) + return 0; + if (lhs == null) + return 1; + if (rhs == null) + return -1; + final Class lc = lhs.getClass(); + final Class rc = rhs.getClass(); + return (lc != rc) ? lc.getName().compareTo(rc.getName()) : ((Comparable)lhs).compareTo(rhs); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/SegmentEntry.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/SegmentEntry.java new file mode 100644 index 0000000000..56af7003cf --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/SegmentEntry.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.unix.UnixPath; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class SegmentEntry> implements Iterable { + + private static final SegmentEntry[] EMPTY = new SegmentEntry[0]; + + String segment; + SegmentEntry[] children = EMPTY; + int size; + + @SuppressWarnings("NullableProblems") + @Override + public Iterator iterator () { + return new Iterator() { + + private int index; + + @Override + public boolean hasNext () { + return index < size; + } + + @SuppressWarnings("unchecked") + @Override + public T next () { + if (index >= size) + throw new NoSuchElementException(); + return (T)children[index++]; + } + + @Override + public void remove () { + if (index == 0) + throw new IllegalStateException(); + SegmentEntry.this.remove(--index); + } + }; + } + + public final boolean isEmpty () { + return size == 0; + } + + public final void clear () { + size = 0; + Arrays.fill(children, null); + } + + @SuppressWarnings({ "unchecked", "SuspiciousSystemArraycopy" }) + public T[] toArray (T[] a) { + if (size > a.length) + a = (T[])Array.newInstance(a.getClass().getComponentType(), size); + System.arraycopy(children, 0, a, 0, size); + return a; + } + + public T getDescendant (UnixPath path) { + return getDescendant(path.stringIterator()); + } + + @SuppressWarnings("unchecked") + public T getDescendant (Iterator segments) { + SegmentEntry parent = this; + while (segments.hasNext()) { + final int index = parent.binarySearch(segments.next()); + if (index < 0) + return null; + parent = parent.children[index]; + } + return (T)parent; + } + + public final PathDescender descentor (UnixPath path) { + return descentor(path.stringIterator()); + } + + @SuppressWarnings("unchecked") + public final PathDescender descentor (Iterator segments) { + return new PathDescender((T)this, segments); + } + + final int binarySearch (String segment) { + final SegmentEntry[] c = children; + int low = 0; + int high = size - 1; + while (low <= high) { + final int mid = (low + high) >>> 1; + final int cmp = c[mid].segment.compareTo(segment); + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + return -(low + 1); // key not found. + } + + final int put (int index, SegmentEntry child) { + if (index < 0) { + index = ~index; + final SegmentEntry[] oc = children; + final int s = size++; + if (s == oc.length) { + final SegmentEntry[] nc = new SegmentEntry[1 << (32 - Integer.numberOfLeadingZeros(s))]; + System.arraycopy(oc, 0, nc, 0, index); + System.arraycopy(oc, index, nc, index + 1, s - index); + children = nc; + nc[index] = child; + } + else { + System.arraycopy(oc, index, oc, index + 1, s - index); + oc[index] = child; + } + } + else + children[index] = child; + return index; + } + + final void remove (int index) { + if (index < 0 || size <= index) + throw new IndexOutOfBoundsException(); + System.arraycopy(children, index + 1, children, index, --size - index); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/UserPrincipalFactory.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/UserPrincipalFactory.java new file mode 100644 index 0000000000..ec21d519e3 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/UserPrincipalFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.attribute.GroupPrincipal; +import com.llamalab.safs.attribute.UserPrincipal; +import com.llamalab.safs.attribute.UserPrincipalLookupService; + +import java.io.IOException; + +public class UserPrincipalFactory extends UserPrincipalLookupService { + + @Override + public UserPrincipal lookupPrincipalByName (String name) throws IOException { + return new User(name); + } + + @Override + public GroupPrincipal lookupPrincipalByGroupName (String group) throws IOException { + return new Group(group); + } + + protected static class User implements UserPrincipal { + + private final String name; + + public User (String name) { + if (name == null) + throw new NullPointerException(); + this.name = name; + } + + @Override + public String getName () { + return name; + } + + @Override + public String toString () { + return super.toString()+"[name="+name+"]"; + } + + @Override + public boolean equals (Object other) { + return other instanceof User && name.equals(((User)other).name); + } + + @Override + public int hashCode () { + return name.hashCode(); + } + + } + + protected static class Group extends User implements GroupPrincipal { + + public Group (String name) { + super(name); + } + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/Utils.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/Utils.java new file mode 100644 index 0000000000..1884e1f08d --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/Utils.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.CopyOption; +import com.llamalab.safs.DirectoryStream; +import com.llamalab.safs.FileVisitOption; +import com.llamalab.safs.FileVisitResult; +import com.llamalab.safs.FileVisitor; +import com.llamalab.safs.Files; +import com.llamalab.safs.LinkOption; +import com.llamalab.safs.OpenOption; +import com.llamalab.safs.Path; +import com.llamalab.safs.SimpleFileVisitor; +import com.llamalab.safs.attribute.BasicFileAttributes; +import com.llamalab.safs.attribute.FileTime; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +@SuppressWarnings("unused") +public final class Utils { + + private static final int BUFFER_SIZE = 8192; + + public static final Charset UTF_8 = Charset.forName("utf-8"); + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + public static final FileTime ZERO_TIME = FileTime.fromMillis(0); + + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + public static final CharSequence[] EMPTY_CHAR_SEQUENCE_ARRAY = new CharSequence[0]; + public static final CopyOption[] EMPTY_COPY_OPTION_ARRAY = new CopyOption[0]; + public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = new LinkOption[0]; + public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = new FileVisitOption[0]; + public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = new OpenOption[0]; + + public static final DirectoryStream.Filter ACCEPT_ALL_FILTER = new DirectoryStream.Filter () { + @Override + public boolean accept (Path entry) throws IOException { + return true; + } + }; + + public static final FileVisitor DELETE_FILE_VISITOR = new SimpleFileVisitor() { + + @Override + public FileVisitResult postVisitDirectory (Path dir, IOException e) throws IOException { + if (e != null) + throw e; + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile (Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed (Path file, IOException e) throws IOException { + return FileVisitResult.CONTINUE; + } + + }; + + private static final Iterator EMPTY_ITERATOR = new Iterator () { + @Override + public boolean hasNext () { + return false; + } + @Override + public Object next () { + return null; + } + @Override + public void remove () { + throw new UnsupportedOperationException(); + } + }; + + private Utils () {} + + public static boolean equals (Object a, Object b) { + return a != null ? a.equals(b) : b == null; + } + + public static boolean contentEquals (CharSequence cs1, CharSequence cs2) { + int l = cs1.length(); + if (l != cs2.length()) + return false; + for (int i = 0; --l >= 0; ++i) { + if (cs1.charAt(i) != cs2.charAt(i)) + return false; + } + return true; + } + + public static boolean contentEqualsIgnoreCase (CharSequence cs1, CharSequence cs2) { + int l = cs1.length(); + if (l != cs2.length()) + return false; + for (int i = 0; --l >= 0; ++i) { + char c1 = cs1.charAt(i); + char c2 = cs2.charAt(i); + if (c1 != c2) { + c1 = Character.toUpperCase(c1); + c2 = Character.toUpperCase(c2); + if (c1 != c2) { + c1 = Character.toLowerCase(c1); + c2 = Character.toLowerCase(c2); + if (c1 != c2) + return false; + } + } + } + return true; + } + + /** + * Available in Java 1.8 + * @see java.lang.String#join(CharSequence, Iterable) + */ + public static String join (CharSequence delimiter, Iterable elements) { + final StringBuilder sb = new StringBuilder(); + CharSequence d = ""; + for (final CharSequence element : elements) { + sb.append(d).append(element); + d = delimiter; + } + return sb.toString(); + } + + /** + * Available in Java 1.7 (Android 4.4) + * @see java.util.Collections#emptyIterator() + */ + @SuppressWarnings("unchecked") + public static Iterator emptyIterator () { + return (Iterator)EMPTY_ITERATOR; + } + + public static Iterator singletonIterator (final E element) { + return new Iterator() { + private boolean started; + @Override + public boolean hasNext () { + return !started; + } + @Override + public E next () { + if (started) + throw new NoSuchElementException(); + started = true; + return element; + } + @Override + public void remove () { + throw new UnsupportedOperationException(); + } + }; + } + + public static DirectoryStream singletonDirectoryStream (final T entry) { + return new AbstractDirectoryStream() { + private boolean started; + @Override + protected T advance () throws IOException { + if (started) + return null; + started = true; + return entry; + } + }; + } + + + public static List listOf (Iterator i) { + final List list = new ArrayList(); + while (i.hasNext()) + list.add(i.next()); + return list; + } + + public static List listOf (Iterable i) { + return listOf(i.iterator()); + } + + + public static void closeQuietly (Closeable c) { + try { + c.close(); + } + catch (Throwable t) { + // ignore + } + } + + /* + public static void unmap (MappedByteBuffer buf) { + try { + final Object cleaner = buf.getClass().getMethod("cleaner").invoke(buf); + cleaner.getClass().getMethod("clean").invoke(cleaner); + } + catch (Throwable t) { + // ignore + } + } + */ + + + public static long transfer (ReadableByteChannel in, WritableByteChannel out) throws IOException { + if (in instanceof FileChannel) + return transfer((FileChannel)in, out); + else + return transfer(in, out, ByteBuffer.allocate(BUFFER_SIZE)); + } + + public static long transfer (ReadableByteChannel in, WritableByteChannel out, ByteBuffer buf) throws IOException { + long written = 0; + while (in.read(buf) != -1) { + buf.flip(); + while (buf.hasRemaining()) + written += out.write(buf); + buf.clear(); + } + return written; + } + + public static long transfer (FileChannel in, WritableByteChannel out) throws IOException { + long remaining = in.size(); + long written = 0; + long b; + while (remaining > 0) { + b = in.transferTo(written, remaining, out); + remaining -= b; + written += b; + } + return written; + } + + public static long transfer (InputStream in, OutputStream out) throws IOException { + return transfer(in, out, new byte[BUFFER_SIZE]); + } + + public static long transfer (InputStream in, OutputStream out, byte[] buf) throws IOException { + long written = 0; + int b; + while ((b = in.read(buf)) != -1) { + out.write(buf, 0, b); + written += b; + } + return written; + } + + public static byte[] readAllBytes (InputStream in, int capacity) throws IOException { + byte[] data = new byte[capacity]; + int size = 0; + for (int b;;) { + while ((b = in.read(data, size, capacity - size)) > 0) + size += b; + if (b < 0 || (b = in.read()) < 0) + break; + if (capacity == Integer.MAX_VALUE) + throw new OutOfMemoryError("Array size exceeded"); + capacity = (int)Math.min(Math.max(capacity * 2L, BUFFER_SIZE), Integer.MAX_VALUE); + data = Arrays.copyOf(data, capacity); + data[size++] = (byte)b; + } + return (size == data.length) ? data : Arrays.copyOf(data, size); + } + + + // http://stackoverflow.com/a/522281/445360 + private static final Pattern RFC3339 = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?(?:Z|([+-]\\d{2}:\\d{2}))"); + public static long parseRfc3339 (CharSequence text) { + final Matcher m = RFC3339.matcher(text); + if (!m.matches()) + throw new IllegalArgumentException(); + String g = m.group(8); + final GregorianCalendar gc = new GregorianCalendar(TimeZone.getTimeZone((g != null) ? g : "UTC"), Locale.US); + //noinspection MagicConstant + gc.set( + Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1, Integer.parseInt(m.group(3)), + Integer.parseInt(m.group(4)), Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6))); + g = m.group(7); + gc.set(Calendar.MILLISECOND, (g != null) ? Integer.parseInt(g)%1000 : 0); + return gc.getTimeInMillis(); + } + + public static String formatRfc3339 (long millis) { + final GregorianCalendar gc = new GregorianCalendar(Utils.UTC, Locale.US); + gc.setTimeInMillis(millis); + //noinspection MagicConstant + return String.format(Locale.US, (gc.get(Calendar.MILLISECOND) != 0) ? "%1$tFT%1$tT.%1$tLZ" : "%1$tFT%1$tTZ", gc); + } + + /* + public static String formatRfc3339 (long time) { + final GregorianCalendar gc = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.US); + gc.setTimeInMillis(time); + return String.format(Locale.US, "%04d-%02d-%02dT%02d:%02d:%02d.%dZ", + gc.get(Calendar.YEAR), gc.get(Calendar.MONTH) + 1, gc.get(Calendar.DAY_OF_MONTH), + gc.get(Calendar.HOUR_OF_DAY), gc.get(Calendar.MINUTE), gc.get(Calendar.SECOND), gc.get(Calendar.MILLISECOND)); + } + */ + + public static FileTime parseFileTime (CharSequence text) { + return FileTime.fromMillis(parseRfc3339(text)); + } + + public static String getFileExtension (Path path) { + final Path fileName = path.getFileName(); + if (fileName != null) { + final String name = fileName.toString(); + final int i = name.lastIndexOf('.'); + if (i > 0 && i < name.length() - 1) + return name.substring(i + 1).toLowerCase(Locale.US); + } + return null; + } + + + /** Must be sorted! */ + private static final char[] REGEX_ESCAPEES = { '$', '(', ')', '*', '+', '.', '?', '[', '\\', ']', '^', '{', '|' }; + + /** + * Only works with unix path separator (/). + */ + public static String globToRegex (String glob, int s, int e) { + final StringBuilder regex = new StringBuilder("^"); + int g = -1; + char c; + while (s < e) { + switch (c = glob.charAt(s++)) { + + case '?': + regex.append("[^/]"); + break; + + case '*': + if (s < e && '*' == glob.charAt(s)) { + regex.append(".*"); + ++s; + } + else + regex.append("[^/]*"); + break; + + case '[': + regex.append("[["); + c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class")); + if ('^' == c) { + regex.append("\\^"); + c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class")); + } + else { + if ('!' == c) { + regex.append('^'); + c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class")); + } + if ('-' == c) { + regex.append('-'); + c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid class")); + } + } + int l = Integer.MAX_VALUE; + while (']' != c) { + if ('/' == c) + throw new PatternSyntaxException("Invalid class character: /", glob, s - 1); + regex.append(c); + if ('-' == c) { + c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid range")); + if (c < l) + throw new PatternSyntaxException("Invalid range", glob, s - 3); + l = Integer.MAX_VALUE; + continue; + } + else + l = c; + c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid range")); + } + regex.append("]&&[^/]]"); + break; + + case '{': + if (g != -1) + throw new PatternSyntaxException("Nested group", glob, s - 1); + regex.append("(?:"); + g = s; + break; + case ',': + regex.append((g != -1) ? '|' : ','); + break; + case '}': + regex.append((g != -1) ? ')' : '}'); + g = -1; + break; + + case '\\': + c = glob.charAt(checkGlobEnd(glob, s++, e, "Invalid escape")); + // fall thu + default: + if (Arrays.binarySearch(REGEX_ESCAPEES, c) >= 0) + regex.append("\\"); + regex.append(c); + } + } + if (g != -1) + throw new PatternSyntaxException("Invalid group", glob, g - 1); + return regex.append('$').toString(); + } + + private static int checkGlobEnd (String glob, int s, int e, String message) { + if (s == e) + throw new PatternSyntaxException(message, glob, s - 1); + return s; + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/WatchEventKind.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/WatchEventKind.java new file mode 100644 index 0000000000..929b1cca44 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/internal/WatchEventKind.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.internal; + +import com.llamalab.safs.WatchEvent; + +public class WatchEventKind implements WatchEvent.Kind { + + private final String name; + private final Class type; + + public WatchEventKind (String name, Class type) { + this.name = name; + this.type = type; + } + + @Override + public final String name () { + return name; + } + + @Override + public final Class type () { + return type; + } + + @Override + public final String toString () { + return name; + } + +} \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/DefaultJavaFileSystemProvider.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/DefaultJavaFileSystemProvider.java new file mode 100644 index 0000000000..7dfbc9d17e --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/DefaultJavaFileSystemProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.java; + +import com.llamalab.safs.FileSystem; +import com.llamalab.safs.FileSystemAlreadyExistsException; +import com.llamalab.safs.Path; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; + +public final class DefaultJavaFileSystemProvider extends JavaFileSystemProvider { + + private final FileSystem fileSystem = new JavaFileSystem(this); + + @Override + public FileSystem getFileSystem (URI uri) { + checkUri(uri); + return fileSystem; + } + + @Override + public FileSystem newFileSystem (URI uri, Map env) throws IOException { + checkUri(uri); + throw new FileSystemAlreadyExistsException(); + } + + @Override + public FileSystem newFileSystem (Path path, Map env) throws IOException { + checkPath(path); + throw new FileSystemAlreadyExistsException(); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/JavaFileSystem.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/JavaFileSystem.java new file mode 100644 index 0000000000..e7dc16f30f --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/JavaFileSystem.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.java; + +import com.llamalab.safs.LinkOption; +import com.llamalab.safs.NoSuchFileException; +import com.llamalab.safs.Path; +import com.llamalab.safs.internal.DefaultFileSystem; +import com.llamalab.safs.spi.FileSystemProvider; +import com.llamalab.safs.unix.AbstractUnixFileSystem; + +import java.io.File; +import java.io.IOException; + +/** + * Only support {@link com.llamalab.safs.unix.UnixPath}. + */ +public class JavaFileSystem extends AbstractUnixFileSystem implements DefaultFileSystem { + + protected volatile Path cacheDirectory; + protected volatile Path currentDirectory; + + public JavaFileSystem (FileSystemProvider provider) { + super(provider); + } + + @Override + public final void close () throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean isOpen () { + return true; + } + + @Override + public boolean isReadOnly () { + return false; + } + + @Override + public Path getCacheDirectory () { + if (cacheDirectory == null) + cacheDirectory = getPathSanitized(System.getProperty("java.io.tmpdir")); + return cacheDirectory; + } + + public final Path getCurrentDirectory () { + if (currentDirectory == null) + currentDirectory = getPathSanitized(System.getProperty("user.dir")); + return currentDirectory; + } + + @Override + protected Path toRealPath (Path path, LinkOption... options) throws IOException { + final File file = path.toFile(); + if (!file.exists()) + throw new NoSuchFileException(path.toString()); + for (final LinkOption option : options) { + if (LinkOption.NOFOLLOW_LINKS == option) + return path.toAbsolutePath().normalize(); + } + return getPathSanitized(file.getCanonicalPath()); + } +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/JavaFileSystemProvider.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/JavaFileSystemProvider.java new file mode 100644 index 0000000000..6bfd11de76 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/JavaFileSystemProvider.java @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.java; + +import com.llamalab.safs.AccessDeniedException; +import com.llamalab.safs.AtomicMoveNotSupportedException; +import com.llamalab.safs.CopyOption; +import com.llamalab.safs.DirectoryNotEmptyException; +import com.llamalab.safs.DirectoryStream; +import com.llamalab.safs.FileAlreadyExistsException; +import com.llamalab.safs.FileStore; +import com.llamalab.safs.FileSystemException; +import com.llamalab.safs.LinkOption; +import com.llamalab.safs.NoSuchFileException; +import com.llamalab.safs.NotDirectoryException; +import com.llamalab.safs.NotLinkException; +import com.llamalab.safs.OpenOption; +import com.llamalab.safs.Path; +import com.llamalab.safs.StandardCopyOption; +import com.llamalab.safs.StandardOpenOption; +import com.llamalab.safs.attribute.BasicFileAttributes; +import com.llamalab.safs.attribute.FileAttribute; +import com.llamalab.safs.attribute.FileTime; +import com.llamalab.safs.channels.SeekableByteChannel; +import com.llamalab.safs.internal.AbstractDirectoryStream; +import com.llamalab.safs.internal.BasicFileAttributeValue; +import com.llamalab.safs.internal.CompleteBasicFileAttributes; +import com.llamalab.safs.internal.FileType; +import com.llamalab.safs.internal.SearchSet; +import com.llamalab.safs.internal.Utils; +import com.llamalab.safs.spi.FileSystemProvider; +import com.llamalab.safs.unix.AbstractUnixFileSystemProvider; +import com.llamalab.safs.unix.UnixPath; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.Set; + +/** + * Only support {@link UnixPath}. + */ +public abstract class JavaFileSystemProvider extends AbstractUnixFileSystemProvider { + + public JavaFileSystemProvider () {} + public JavaFileSystemProvider (FileSystemProvider provider) {} + + @Override + public String getScheme () { + return "file"; + } + + @Override + public FileStore getFileStore (Path path) throws IOException { + // TODO: possible? + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSameFile (Path path1, Path path2) throws IOException { + if (path1.equals(path2)) + return true; + return getPathType().isInstance(path1) && getPathType().isInstance(path2) + && path1.getFileSystem().equals(path2.getFileSystem()) + && isSameFile(path1.toFile(), path2.toFile()); + } + + @Override + public boolean isHidden (Path path) throws IOException { + checkPath(path); + return ((UnixPath)path).isHidden(); + } + + @Override + public void createDirectory (Path dir, FileAttribute... attrs) throws IOException { + checkPath(dir); + createDirectory(dir.toFile(), attrs); + } + + @Override + public void delete (Path path) throws IOException { + checkPath(path); + delete(path.toFile(), false); + } + + @Override + public void copy (Path source, Path target, CopyOption... options) throws IOException { + checkPath(source); + checkPath(target); + transfer(source, target, false, new SearchSet(options)); + } + + @Override + public void move (Path source, Path target, CopyOption... options) throws IOException { + checkPath(source); + checkPath(target); + transfer(source, target, true, new SearchSet(options)); + } + + // TODO: symbolic links + private void transfer (Path source, Path target, boolean move, Set options) throws IOException { + final File sourceFile = source.toFile(); + final BasicFileAttributes sourceAttrs = readBasicFileAttributes(sourceFile); + final File targetFile = target.toFile(); + if (sourceFile.getCanonicalPath().equals(targetFile.getCanonicalPath())) + return; + // atomic + if (move && options.contains(StandardCopyOption.ATOMIC_MOVE)) { + if (sourceFile.renameTo(targetFile)) + return; + throw new AtomicMoveNotSupportedException(source.toString(), target.toString(), "Rename failed"); + } + // delete target + if (options.contains(StandardCopyOption.REPLACE_EXISTING)) + delete(targetFile, true); // throws DirectoryNotEmptyException + else if (targetFile.exists()) + throw new FileAlreadyExistsException(target.toString()); + // rename + if (move && sourceFile.renameTo(targetFile)) + return; + // transfer + if (sourceAttrs.isDirectory()) { + if (move && isNonEmptyDirectory(sourceFile)) + throw new DirectoryNotEmptyException(source.toString()); + createDirectory(target); + } + else + copyFile(sourceFile, targetFile); + try { + if (options.contains(StandardCopyOption.COPY_ATTRIBUTES)) + setLastModifiedTime(targetFile, sourceAttrs.lastModifiedTime()); + if (move) + delete(sourceFile, false); + } + catch (IOException e) { + //noinspection ResultOfMethodCallIgnored + targetFile.delete(); + throw e; + } + catch (RuntimeException e) { + //noinspection ResultOfMethodCallIgnored + targetFile.delete(); + throw e; + } + } + + @Override + public InputStream newInputStream (Path path, OpenOption... options) throws IOException { + checkPath(path); + return newInputStream(path.toFile(), (options.length == 0) ? DEFAULT_NEW_INPUT_STREAM_OPTIONS : new SearchSet(options)); + } + + // TODO: LinkOption.NOFOLLOW_LINKS + private InputStream newInputStream (File file, Set options) throws IOException { + if (options.contains(StandardOpenOption.WRITE)) + throw new IllegalArgumentException(); + try { + return new FileInputStream(file); + } + catch (IOException e) { + throw toProperException(e, file.toString(), null); + } + } + + @Override + public OutputStream newOutputStream (Path path, OpenOption... options) throws IOException { + checkPath(path); + return newOutputStream(path.toFile(), (options.length == 0) ? DEFAULT_NEW_OUTPUT_STREAM_OPTIONS : new SearchSet(options)); + } + + // TODO: LinkOption.NOFOLLOW_LINKS + private OutputStream newOutputStream (File file, Set options) throws IOException { + if (!options.contains(StandardOpenOption.WRITE)) + throw new IllegalArgumentException(); + try { + checkCreateOptions(file, options); + return new FileOutputStream(file, options.contains(StandardOpenOption.APPEND)); + } + catch (IOException e) { + throw toProperException(e, file.toString(), null); + } + } + + @Override + public SeekableByteChannel newByteChannel (Path path, Set options, FileAttribute... attrs) throws IOException { + checkPath(path); + return newByteChannel(path.toFile(), options, attrs); + } + + // TODO: LinkOption.NOFOLLOW_LINKS + private SeekableByteChannel newByteChannel (File file, Set options, FileAttribute... attrs) throws IOException { + try { + if (!options.contains(StandardOpenOption.WRITE)) + return new SeekableByteChannelWrapper(new RandomAccessFile(file, "r").getChannel(), false); + checkCreateOptions(file, options); + final RandomAccessFile raf = new RandomAccessFile(file, toModeString(options)); + if (options.contains(StandardOpenOption.TRUNCATE_EXISTING)) { + try { + raf.setLength(0); + } + catch (IOException e) { + Utils.closeQuietly(raf); + throw e; + } + } + return new SeekableByteChannelWrapper(raf.getChannel(), options.contains(StandardOpenOption.APPEND)); + } + catch (IOException e) { + throw toProperException(e, file.toString(), null); + } + } + + private static String toModeString (Set options) { + if (options.contains(StandardOpenOption.SYNC)) + return "rws"; + if (options.contains(StandardOpenOption.DSYNC)) + return "rwd"; + return "rw"; + } + + private static void checkCreateOptions (File file, Set options) throws IOException { + if (options.contains(StandardOpenOption.CREATE_NEW)) { + if (file.exists()) + throw new FileAlreadyExistsException(file.toString()); + } + else if (!options.contains(StandardOpenOption.CREATE)) { + if (!file.exists()) + throw new NoSuchFileException(file.toString()); + } + } + + /* + private RandomAccessFile newRandomAccessFile (File file, Set options) throws IOException { + try { + if (!options.contains(StandardOpenOption.WRITE)) + return new RandomAccessFile(file, toModeString(options)); + final RandomAccessFile raf = new RandomAccessFile(file, toModeString(options)); + if (options.contains(StandardOpenOption.TRUNCATE_EXISTING)) { + try { + raf.setLength(0); + } + catch (IOException e) { + Utils.closeQuietly(raf); + throw e; + } + } + return raf; + } + catch (IOException e) { + throw toProperException(e, file.toString(), null); + } + } + */ + + /* + @Override + public void createSymbolicLink (Path link, Path target) throws IOException { + checkPaths(link, target); + createSymbolicLink(link.toFile(), target.toFile()); + } + + protected void createSymbolicLink (File link, File target) throws IOException { + final Process process = Runtime.getRuntime().exec(new String[] { "ln", "-s", target.toString(), link.toString() }); + try { + if (0 != process.waitFor()) { + if (target.exists()) + throw new FileAlreadyExistsException(target.toString()); + else + throw new IOException("ln command failure"); + } + } + catch (InterruptedException e) { + throw (IOException)new InterruptedIOException().initCause(e); + } + finally { + process.destroy(); + } + } + */ + + @Override + public Path readSymbolicLink (Path link) throws IOException { + final Path parent = link.getParent(); + if (parent != null) + link = parent.toRealPath().resolve(link.getFileName()); + final Path real = link.toRealPath(); + if (real.equals(link.toAbsolutePath())) + throw new NotLinkException(link.toString()); + return real; + } + + /** + * May be overridden with a faster implementation. + */ + protected boolean isSymbolicLink (Path path) { + return isSymbolicLink(path.toFile()); + } + + private boolean isSymbolicLink (File file) { + try { + final File parent = file.getParentFile(); + if (parent != null) + file = new File(parent.getCanonicalPath(), file.getName()); + return !file.getCanonicalPath().equals(file.getAbsolutePath()); + } + catch (IOException e) { + return false; + } + } + + @SuppressWarnings("unchecked") + @Override + public A readAttributes (Path path, Class type, LinkOption... options) throws IOException { + checkPath(path); + if (BasicFileAttributes.class != type) + throw new UnsupportedOperationException("Unsupported type: "+type); + return (A)readBasicFileAttributes(path.toFile(), options); + } + + protected BasicFileAttributes readBasicFileAttributes (File file, LinkOption... options) throws IOException { + for (final LinkOption option : options) { + if (LinkOption.NOFOLLOW_LINKS == option) { + if (!isSymbolicLink(file)) + break; + return new CompleteBasicFileAttributes(null, FileType.SYMBOLIC_LINK, 0, Utils.ZERO_TIME, Utils.ZERO_TIME, Utils.ZERO_TIME); + } + } + if (!file.exists()) + throw new NoSuchFileException(file.toString()); + final FileType fileType; + if (file.isDirectory()) + fileType = FileType.DIRECTORY; + else if (file.isFile()) + fileType = FileType.REGULAR_FILE; + else + fileType = FileType.OTHER; + return new CompleteBasicFileAttributes( + null, + fileType, + file.length(), + Utils.ZERO_TIME, + FileTime.fromMillis(file.lastModified()), + Utils.ZERO_TIME); + } + + @Override + protected void setAttributes (Path path, Set> attrs, LinkOption... options) throws IOException { + checkPath(path); + for (final FileAttribute attr : attrs) { + if (attr instanceof BasicFileAttributeValue) { + switch (((BasicFileAttributeValue)attr).type()) { + case lastModifiedTime: + setLastModifiedTime(path.toFile(), (FileTime)attr.value()); + continue; + } + } + throw new UnsupportedOperationException("Attribute: "+attr.name()); + } + } + + protected void setLastModifiedTime (File file, FileTime value) throws IOException { + if (!file.setLastModified(value.toMillis())) + throw new FileSystemException(file.toString(), null, "Failed to set last modified time"); + } + + @Override + public DirectoryStream newDirectoryStream (final Path dir, final DirectoryStream.Filter filter) throws IOException { + checkPath(dir); + if (filter == null) + throw new NullPointerException("filter"); + try { + final File dirFile = dir.toFile(); + final String[] files = dirFile.list(); + if (files == null) { + if (dirFile.exists() && !dirFile.canRead()) + throw new AccessDeniedException(dir.toString()); + throw new NotDirectoryException(dir.toString()); + } + return new AbstractDirectoryStream() { + private int index; + @Override + protected Path advance () throws IOException { + while (index < files.length) { + final Path entry = dir.resolve(files[index++]); + if (filter.accept(entry)) + return entry; + } + return null; + } + }; + } + catch (IOException e) { + throw toProperException(e, dir.toString(), null); + } + } + + protected final void createDirectory (File dir, FileAttribute... attrs) throws IOException { + if (!dir.mkdir()) { + if (dir.exists()) { + if (!dir.canWrite()) + throw new AccessDeniedException(dir.toString()); + throw new FileAlreadyExistsException(dir.toString()); + } + throw new FileSystemException(dir.toString(), null, "Failed to create directory"); + } + } + + protected final void delete (File file, boolean ifExists) throws IOException { + if (!file.delete()) { + if (isNonEmptyDirectory(file)) + throw new DirectoryNotEmptyException(file.toString()); + if (file.exists()) { + if (!file.canWrite()) + throw new AccessDeniedException(file.toString()); + throw new FileSystemException(file.toString(), null, "Failed to delete file"); + } + else if (!ifExists) + throw new NoSuchFileException(file.toString()); + } + } + + protected final void copyFile (File source, File target) throws IOException { + final FileChannel in; + try { + in = new FileInputStream(source).getChannel(); + } + catch (IOException e) { + throw toProperException(e, source.toString(), null); + } + try { + final FileChannel out; + try { + out = new FileOutputStream(target).getChannel(); + } + catch (IOException e) { + throw toProperException(e, target.toString(), null); + } + try { + Utils.transfer(in, out); + } + catch (IOException e) { + throw toProperException(e, source.toString(), target.toString()); + } + finally { + out.close(); + } + } + finally { + in.close(); + } + } + + protected final boolean isSameFile (File file1, File file2) throws IOException { + return file1.getCanonicalPath().equals(file2.getCanonicalPath()); + } + + protected final boolean isNonEmptyDirectory (File dir) { + final String[] names = dir.list(new FilenameFilter() { + private boolean found; + @Override + public boolean accept (File dir, String filename) { + if (found) + return false; + return found = true; + } + }); + return names != null && names.length != 0; + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/SeekableByteChannelWrapper.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/SeekableByteChannelWrapper.java new file mode 100644 index 0000000000..23c2e88f4a --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/java/SeekableByteChannelWrapper.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.java; + +import com.llamalab.safs.channels.SeekableByteChannel; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +final class SeekableByteChannelWrapper implements SeekableByteChannel { + + private final FileChannel fc; + private final boolean append; + + public SeekableByteChannelWrapper (FileChannel fc, boolean append) { + this.fc = fc; + this.append = append; + } + + @Override + public void close () throws IOException { + fc.close(); + } + + @Override + public boolean isOpen () { + return fc.isOpen(); + } + + @Override + public int read (ByteBuffer dst) throws IOException { + return fc.read(dst); + } + + @Override + public int write (ByteBuffer src) throws IOException { + if (append) + fc.position(fc.size()); + return fc.write(src); + } + + @Override + public long position () throws IOException { + return fc.position(); + } + + @Override + public SeekableByteChannel position (long newPosition) throws IOException { + fc.position(newPosition); + return this; + } + + @Override + public long size () throws IOException { + return fc.size(); + } + + @Override + public SeekableByteChannel truncate (long size) throws IOException { + fc.truncate(size); + return this; + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/kotlin/io/path/PathUtils.kt b/mirai-console/backend/mirai-console/src/com/llamalab/safs/kotlin/io/path/PathUtils.kt new file mode 100644 index 0000000000..b4a0c6256b --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/kotlin/io/path/PathUtils.kt @@ -0,0 +1,28 @@ +package com.llamalab.safs.kotlin.io.path +import com.llamalab.safs.LinkOption +import com.llamalab.safs.Path +import com.llamalab.safs.Files +import com.llamalab.safs.OpenOption +import com.llamalab.safs.attribute.FileAttribute +import java.nio.charset.Charset + +public inline fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options) +public inline fun Path.readBytes(): ByteArray { + return Files.readAllBytes(this) +} +public inline fun Path.writeBytes(array: ByteArray, vararg options: OpenOption): Unit { + Files.write(this, array, *options) +} +public inline fun Path.createDirectories(vararg attributes: FileAttribute<*>): Path = + Files.createDirectories(this, *attributes) + +public fun createTempDirectory(directory: Path?, prefix: String? = null, vararg attributes: FileAttribute<*>): Path = + if (directory != null) + Files.createTempDirectory(directory, prefix, *attributes) + else + Files.createTempDirectory(prefix, *attributes) +public inline fun createTempDirectory(prefix: String? = null, vararg attributes: FileAttribute<*>): Path = Files.createTempDirectory(prefix, *attributes) + +public fun Path.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption) { + Files.newOutputStream(this, *options).writer(charset).use { it.append(text) } +} \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/spi/FileSystemProvider.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/spi/FileSystemProvider.java new file mode 100644 index 0000000000..8596f6f892 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/spi/FileSystemProvider.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.spi; + +import com.llamalab.safs.CopyOption; +import com.llamalab.safs.DirectoryStream; +import com.llamalab.safs.FileStore; +import com.llamalab.safs.FileSystem; +import com.llamalab.safs.FileSystems; +import com.llamalab.safs.LinkOption; +import com.llamalab.safs.NoSuchFileException; +import com.llamalab.safs.OpenOption; +import com.llamalab.safs.Path; +import com.llamalab.safs.attribute.BasicFileAttributes; +import com.llamalab.safs.attribute.FileAttribute; +import com.llamalab.safs.attribute.FileAttributeView; +import com.llamalab.safs.channels.SeekableByteChannel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; + +public abstract class FileSystemProvider { + + protected FileSystemProvider () {} + + public static List installedProviders () { + return InstalledFileSystemProvidersHolder.providers; + } + + public abstract String getScheme (); + public abstract Path getPath (URI uri); + public abstract FileSystem getFileSystem (URI uri); + public abstract FileSystem newFileSystem (Path path, Map env) throws IOException; + public abstract FileSystem newFileSystem (URI uri, Map env) throws IOException; + public abstract FileStore getFileStore (Path path) throws IOException; + public abstract boolean isSameFile (Path path1, Path path2) throws IOException; + public abstract boolean isHidden (Path path) throws IOException; + public abstract void createDirectory (Path dir, FileAttribute... attrs) throws IOException; + public abstract void delete (Path path) throws IOException; + public boolean deleteIfExists (Path path) throws IOException { + try { + delete(path); + return true; + } + catch (NoSuchFileException e) { + return false; + } + } + public abstract void copy (Path source, Path target, CopyOption... options) throws IOException; + public abstract void move (Path source,Path target, CopyOption... options) throws IOException; + public abstract InputStream newInputStream (Path path, OpenOption... options) throws IOException; + public abstract OutputStream newOutputStream (Path path, OpenOption... options) throws IOException; + public abstract SeekableByteChannel newByteChannel (Path path, Set options, FileAttribute... attrs) throws IOException; + /* + public void createSymbolicLink (Path link, Path target) throws IOException { + throw new UnsupportedOperationException(); + } + */ + public Path readSymbolicLink (Path link) throws IOException { + throw new UnsupportedOperationException(); + } + public abstract A readAttributes (Path path, Class type, LinkOption... options) throws IOException; + public abstract Map readAttributes (Path path, String attributes, LinkOption... options) throws IOException; + public abstract void setAttribute (Path path, String attribute, Object value, LinkOption... options) throws IOException; + public abstract V getFileAttributeView (Path path, Class type, LinkOption... options); + + public abstract DirectoryStream newDirectoryStream (Path dir, DirectoryStream.Filter filter) throws IOException; + + private static final class InstalledFileSystemProvidersHolder { + + static final List providers = loadInstalledProviders(); + + private static List loadInstalledProviders () { + final List providers = new ArrayList(); + providers.add(FileSystems.getDefault().provider()); + for (final FileSystemProvider provider : ServiceLoader.load(FileSystemProvider.class, FileSystemProvider.class.getClassLoader())) { + if (!"file".equalsIgnoreCase(provider.getScheme())) + providers.add(provider); + } + return Collections.unmodifiableList(providers); + } + + } // class InstalledFileSystemProvidersHolder +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/spi/FileTypeDetector.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/spi/FileTypeDetector.java new file mode 100644 index 0000000000..e641cd6c33 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/spi/FileTypeDetector.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.spi; + +import com.llamalab.safs.Path; + +import java.io.IOException; + +/** + * System.setProperty("content.types.user.table", "/path/to/property/file"​); + */ +public abstract class FileTypeDetector { + + protected FileTypeDetector () {} + + public abstract String probeContentType (Path path) throws IOException; +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/AbstractUnixFileSystem.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/AbstractUnixFileSystem.java new file mode 100644 index 0000000000..5b2ee0c278 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/AbstractUnixFileSystem.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.unix; + +import com.llamalab.safs.FileStore; +import com.llamalab.safs.FileSystem; +import com.llamalab.safs.LinkOption; +import com.llamalab.safs.Path; +import com.llamalab.safs.PathMatcher; +import com.llamalab.safs.WatchService; +import com.llamalab.safs.attribute.UserPrincipalLookupService; +import com.llamalab.safs.internal.BasicFileAttribute; +import com.llamalab.safs.internal.Utils; +import com.llamalab.safs.spi.FileSystemProvider; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; +import java.util.regex.Pattern; + +public abstract class AbstractUnixFileSystem extends FileSystem { + + protected final FileSystemProvider provider; + private volatile Path emptyDirectory; + private volatile Path rootDirectory; + + public AbstractUnixFileSystem (FileSystemProvider provider) { + this.provider = provider; + } + + @Override + public final FileSystemProvider provider () { + return provider; + } + + @Override + public final Path getPath (String first, String... more) { + return getPathSanitized(UnixPath.sanitize(first, more)); + } + + public final Path getPath (String first) { + return getPathSanitized(UnixPath.sanitize(first, Utils.EMPTY_STRING_ARRAY)); + } + + protected Path getPathSanitized (String path) { + return new UnixPath(this, path); + } + + @Override + public PathMatcher getPathMatcher (String syntaxAndPattern) { + final Pattern pattern; + if (syntaxAndPattern.startsWith("regex:")) + pattern = Pattern.compile(syntaxAndPattern.substring(6)); + else if (syntaxAndPattern.startsWith("glob:")) + pattern = Pattern.compile(Utils.globToRegex(syntaxAndPattern, 5, syntaxAndPattern.length())); + else + throw new UnsupportedOperationException(syntaxAndPattern); + return new PathMatcher() { + @Override + public boolean matches (Path path) { + return pattern.matcher(path.toString()).matches(); + } + }; + } + + protected abstract Path toRealPath (Path path, LinkOption... options) throws IOException; + + @Override + public final String getSeparator() { + return "/"; + } + + @Override + public Set supportedFileAttributeViews() { + return Collections.singleton(BasicFileAttribute.VIEW_NAME); + } + + @Override + public Iterable getFileStores () { + return Collections.emptySet(); + } + + @Override + public final Iterable getRootDirectories () { + return Collections.singleton(getRootDirectory()); + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService () { + throw new UnsupportedOperationException(); + } + + @Override + public WatchService newWatchService () throws IOException { + throw new UnsupportedOperationException(); + } + + public abstract Path getCurrentDirectory (); + + public final Path getEmptyDirectory () { + if (emptyDirectory == null) + emptyDirectory = getPathSanitized(""); + return emptyDirectory; + } + + public final Path getRootDirectory () { + if (rootDirectory == null) + rootDirectory = getPathSanitized("/"); + return rootDirectory; + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/AbstractUnixFileSystemProvider.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/AbstractUnixFileSystemProvider.java new file mode 100644 index 0000000000..d1918b3838 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/AbstractUnixFileSystemProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.unix; + +import com.llamalab.safs.Path; +import com.llamalab.safs.internal.AbstractFileSystemProvider; +import com.llamalab.safs.internal.Utils; + +import java.net.URI; + +public abstract class AbstractUnixFileSystemProvider extends AbstractFileSystemProvider { + + @Override + protected Class getPathType () { + return UnixPath.class; + } + + @Override + public Path getPath (URI uri) { + if (!uri.isAbsolute() || uri.isOpaque() || uri.getAuthority() != null || uri.getFragment() != null || uri.getQuery() != null) + throw new IllegalArgumentException(); + return new UnixPath((AbstractUnixFileSystem)getFileSystem(uri), UnixPath.sanitize(uri.getPath(), Utils.EMPTY_STRING_ARRAY)); + } + +} diff --git a/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/UnixPath.java b/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/UnixPath.java new file mode 100644 index 0000000000..3f5674a1a7 --- /dev/null +++ b/mirai-console/backend/mirai-console/src/com/llamalab/safs/unix/UnixPath.java @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2019 Henrik Lindqvist + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.llamalab.safs.unix; + +import com.llamalab.safs.LinkOption; +import com.llamalab.safs.Path; +import com.llamalab.safs.ProviderMismatchException; +import com.llamalab.safs.WatchEvent; +import com.llamalab.safs.WatchKey; +import com.llamalab.safs.WatchService; +import com.llamalab.safs.internal.Utils; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class UnixPath implements Path { + + private final AbstractUnixFileSystem fs; + private final String path; + private volatile short[] nameOffsets; // lazy + + protected UnixPath (AbstractUnixFileSystem fs, String path) { + this.fs = fs; + this.path = path; + } + + protected UnixPath (UnixPath other) { + this(other.fs, other.path); + this.nameOffsets = other.nameOffsets; + } + + @Override + public final AbstractUnixFileSystem getFileSystem () { + return fs; + } + + @Override + public final String toString () { + return path; + } + + @Override + public final int hashCode () { + int hc = 17; + hc = 37*hc + fs.hashCode(); + hc = 37*hc + path.hashCode(); + return hc; + } + + @Override + public boolean equals (Object obj) { + if (this == obj) + return true; + if (!(obj instanceof UnixPath)) + return false; + final UnixPath other = (UnixPath)obj; + return fs.equals(other.fs) + && path.equals(other.path); + } + + @SuppressWarnings("NullableProblems") + @Override + public int compareTo (Path other) { + return path.compareTo(other.toString()); + } + + @Override + public final boolean isAbsolute () { + return path.startsWith("/"); + } + + public final boolean isHidden () { + return path.startsWith("."); + } + + public final boolean isEmpty () { + return path.isEmpty(); + } + + public final boolean isRoot () { + return path.equals("/"); + } + + /** + * @return true if . or .. + */ + public final boolean isDots () { + return ".".equals(path) || "..".equals(path); + } + + private static final short[] ZERO_NAME_OFFSETS = new short[] { 0 }; + private static final short[] EMPTY_NAME_OFFSETS = new short[0]; + + private short[] getNameOffsets () { + if (nameOffsets != null) + return nameOffsets; + final String path = this.path; + final int length = path.length(); + if (length == 0) + return nameOffsets = ZERO_NAME_OFFSETS; + int count = 0; + int index = 0; + while (index < length) { + if ('/' != path.charAt(index)) { + if (index++ > 0xFFFF) + throw new ArrayStoreException(); + ++count; + while (index < length && '/' != path.charAt(index)) + ++index; + } + else + ++index; + } + if (count == 0) + return nameOffsets = EMPTY_NAME_OFFSETS; + final short[] offsets = new short[count]; + index = 0; + count = 0; + while (index < length) { + if ('/' != path.charAt(index)) { + offsets[count++] = (short)index++; + while (index < length && '/' != path.charAt(index)) + ++index; + } + else + ++index; + } + return nameOffsets = offsets; + } + + @Override + public Path getRoot () { + return isAbsolute() ? fs.getRootDirectory() : null; + } + + @Override + public Path getParent () { + final short[] offsets = getNameOffsets(); + final int count = offsets.length; + if (count == 0) + return null; + final int end = (offsets[count - 1] & 0xFFFF) - 1; + if (end <= 0) + return getRoot(); + return fs.getPathSanitized(path.substring(0, end)); + } + + /* + public final Path getAncestor (int endIndex) { + final short[] offsets = getNameOffsets(); + final int count = offsets.length; + if (endIndex < 0 || count < endIndex) + throw new IllegalArgumentException(); + if (count == 0) + return null; + if (endIndex == count) + return this; + final int end = (offsets[endIndex] & 0xFFFF) - 1; + if (end <= 0) + return getRoot(); + return fs.getPathSanitized(path.substring(0, end)); + } + */ + + @Override + public Path getFileName () { + final short[] offsets = getNameOffsets(); + final int count = offsets.length; + if (count == 0) + return null; + if (count == 1 && path.length() != 0 && '/' != path.charAt(0)) + return this; + return fs.getPathSanitized(path.substring(offsets[count - 1] & 0xFFFF)); + } + + public final int getNameCount () { + return getNameOffsets().length; + } + + public Path getName (int index) { + return subpath(index, index + 1); + } + + public Path subpath (int beginIndex, int endIndex) { + final short[] offsets = getNameOffsets(); + final int count = offsets.length; + if (beginIndex < 0 || count < endIndex || endIndex <= beginIndex) + throw new IllegalArgumentException(); + if (endIndex < count) + return fs.getPathSanitized(path.substring(offsets[beginIndex] & 0xFFFF, (offsets[endIndex] & 0xFFFF) - 1)); + else + return fs.getPathSanitized(path.substring(offsets[beginIndex] & 0xFFFF)); + } + + @Override + public final boolean startsWith (Path other) { + return fs.equals(other.getFileSystem()) + && startsWithSanitized(((UnixPath)other).path); + } + + @Override + public final boolean startsWith (String other) { + return startsWithSanitized(sanitize(other, Utils.EMPTY_STRING_ARRAY)); + } + + private boolean startsWithSanitized (String other) { + if (!path.startsWith(other)) + return false; + final int end = other.length(); + return end == path.length() || '/' == path.charAt(end) || "/".equals(other); + } + + public final boolean endsWith (Path other) { + return fs.equals(other.getFileSystem()) + && endsWithSanitized(((UnixPath)other).path); + } + + public final boolean endsWith (String other) { + return endsWithSanitized(sanitize(other, Utils.EMPTY_STRING_ARRAY)); + } + + private boolean endsWithSanitized (String other) { + if (!path.endsWith(other)) + return false; + final int start = path.length() - other.length(); + return start == 0 || '/' == path.charAt(start - 1); + } + + @SuppressWarnings("NullableProblems") + @Override + public Iterator iterator () { + final short[] offsets = getNameOffsets(); + final int count = offsets.length; + if (count == 0) + return Utils.emptyIterator(); + return new NameIterator(path, offsets, count) { + @Override + protected Path next (String path, int start, int end) { + return fs.getPathSanitized(path.substring(start, end)); + } + }; + } + + public final Iterator charSequenceIterator () { + final short[] offsets = getNameOffsets(); + final int count = offsets.length; + if (count == 0) + return Utils.emptyIterator(); + return new NameIterator(path, offsets, count) { + @Override + protected CharSequence next (String path, int start, int end) { + return path.subSequence(start, end); + } + }; + } + + public final Iterator stringIterator () { + final short[] offsets = getNameOffsets(); + final int count = offsets.length; + if (count == 0) + return Utils.emptyIterator(); + return new NameIterator(path, offsets, count) { + @Override + protected String next (String path, int start, int end) { + return path.substring(start, end); + } + }; + } + + // FIXME: horrid + @Override + public Path normalize () { + CharSequence[] names = Utils.EMPTY_CHAR_SEQUENCE_ARRAY; + int end = 0; + for (Iterator i = charSequenceIterator(); i.hasNext();) { + final CharSequence n = i.next(); + if ("..".contentEquals(n) && end > 0) + --end; + else if (!".".contentEquals(n)){ + if (names.length == end) + names = Arrays.copyOf(names, Math.max(end*2, 8)); + names[end++] = n; + } + } + if (end == 0) + return isAbsolute() ? fs.getRootDirectory() : fs.getEmptyDirectory(); + return fs.getPath(join(names, 0, end, isAbsolute() ? "/" : "")); + } + + // FIXME: horrid + @Override + public Path relativize (Path other) { + checkPath(other); + if (isAbsolute() != other.isAbsolute()) + throw new IllegalArgumentException("Absolute vs relative"); + //if (equals(that)) + // return fs.getEmptyDirectory(); + final Iterator bi = charSequenceIterator(); + final Iterator ci = ((UnixPath)other).charSequenceIterator(); + final RelativizeHelper h = new RelativizeHelper(); + CharSequence bn, cn = null; + out: while (bi.hasNext() && ci.hasNext()) { + while (".".contentEquals(bn = bi.next())) + if (!bi.hasNext()) break out; + while (".".contentEquals(cn = ci.next())) + if (!ci.hasNext()) break out; + if (!Utils.contentEquals(bn, cn)) { + h.base(bn); + break; + } + h.add(bn); + ++h.start; + cn = null; + } + while (bi.hasNext()) + if (!".".contentEquals(bn = bi.next())) h.base(bn); + if (cn != null && !".".contentEquals(cn)) + h.child(cn); + while (ci.hasNext()) + if (!".".contentEquals(cn = ci.next())) h.child(cn); + return fs.getPathSanitized(join(h.names, h.start, h.end, "")); + } + + private static final class RelativizeHelper { + public CharSequence[] names = new CharSequence[8]; + public int start = 0, end = 0; + public void base (CharSequence value) { + if ("..".contentEquals(value) && start > 0) + --start; + else + add(".."); + } + public void child (CharSequence value) { + if ("..".contentEquals(value) && start < end && !"..".contentEquals(names[end - 1])) + --end; + else + add(value); + } + public void add (CharSequence value) { + if (names.length == end) + names = Arrays.copyOf(names, end*2); + names[end++] = value; + } + } // class RelativizeHelper + + @Override + public Path resolve (Path other) { + checkPath(other); + if (other.isAbsolute()) + return other; + if (((UnixPath)other).isEmpty()) + return this; + return fs.getPath(path, ((UnixPath)other).path); + } + + @Override + public Path resolve (String other) { + if (other.startsWith("/")) + return fs.getPath(other); + if (other.isEmpty()) + return this; + return fs.getPath(path, other); + } + + public Path resolveSibling (Path other) { + checkPath(other); + final Path parent = getParent(); + return (parent != null) ? parent.resolve(other) : other; + } + + public Path resolveSibling (String other) { + final Path parent = getParent(); + return (parent != null) ? parent.resolve(other) : fs.getPath(other); + } + + @Override + public Path toAbsolutePath () { + return isAbsolute() ? this : fs.getCurrentDirectory().resolve(this); + } + + @Override + public Path toRealPath (LinkOption... options) throws IOException { + return fs.toRealPath(this, options); + } + + @Override + public File toFile () { + return new File(path); + } + + @Override + public URI toUri () { + try { + return new URI(fs.provider().getScheme(), toAbsolutePath().toString(), null); + } + catch (Throwable t) { + throw new IllegalStateException(t); // shouldn't happen + } + } + + private static final WatchEvent.Modifier[] EMPTY_MODIFIER_ARRAY = new WatchEvent.Modifier[0]; + + @Override + public final WatchKey register (WatchService service, WatchEvent.Kind... kinds) throws IOException { + return register(service, kinds, EMPTY_MODIFIER_ARRAY); + } + + @Override + public WatchKey register (WatchService service, WatchEvent.Kind[] kinds, WatchEvent.Modifier... modifiers) throws IOException { + throw new UnsupportedOperationException(); + } + + private static void checkPath (Path path) { + if (!(path instanceof UnixPath)) + throw (path == null) ? new NullPointerException() : new ProviderMismatchException(); + } + + /* + private static String sanitize (String path) { + return sanitize(path, Utils.EMPTY_STRING_ARRAY); + } + */ + + /** + * Removes double and tail slash + */ + static String sanitize (String first, String[] more) { + CharSequence parent = ""; + for (int i = -1, count = more.length;;) { + parent = sanitizeChunk(parent, first); + if (++i == count) + break; + first = more[i]; + } + return parent.toString(); + } + + private static CharSequence sanitizeChunk (CharSequence parent, String path) { + int end = path.length(); + if (end == 0) + return parent; // empty + while (end > 0 && '/' == path.charAt(end - 1)) --end; + if (end == 0) + return (parent.length() == 0) ? "/" : parent; // root + // At this point it's neither empty nor root. + int start = 0; + while (start < end && '/' == path.charAt(start)) ++start; + final int parentLength = parent.length(); + int index = path.indexOf("//", start + 1); + if (index == -1 || index >= end) { + // no // + if (parentLength == 0) { + if (start > 0) + --start; // keep initial / + return path.substring(start, end); + } + if (parentLength == 1 && '/' == parent.charAt(0)) { + // root parent + if (start > 0) + return path.substring(start - 1, end); + else + return new StringBuilder("/").append(path, start, end); + } + final StringBuilder sb = (parent instanceof StringBuilder) ? (StringBuilder)parent : new StringBuilder(parent); + return sb.append('/').append(path, start, end); + } + // found // + final StringBuilder sb = (parent instanceof StringBuilder) ? (StringBuilder)parent : new StringBuilder(parent); + if (parentLength == 0) { + if (start > 0) + --start; // keep initial / + } + else if (parentLength != 1 || '/' != parent.charAt(0)) { + // not root parent + sb.append('/'); + } + while (++index < end) { + sb.append(path, start, index); + while (index < end && '/' == path.charAt(index)) ++index; + start = index; + } + return sb.append(path, start, index); + } + + private static String join (CharSequence[] cs, int start, int end, String prefix) { + final StringBuilder sb = new StringBuilder(); + while (start < end) { + sb.append(prefix).append(cs[start++]); + prefix = "/"; + } + return sb.toString(); + } + + private static abstract class NameIterator implements Iterator { + + private final String path; + private final short[] offsets; + private final int end; + private int index; + + public NameIterator (String path, short[] offsets, int count) { + this.path = path; + this.offsets = offsets; + this.end = count - 1; + } + + @Override + public final boolean hasNext () { + return index <= end; + } + + @Override + public final T next () { + final int i = index; + if (i > end) + throw new NoSuchElementException(); + ++index; + return next( + path, + offsets[i] & 0xFFFF, + (i < end) ? (offsets[i + 1] & 0xFFFF) - 1 : path.length()); + } + + @Override + public final void remove () { + throw new UnsupportedOperationException(); + } + + protected abstract T next (String path, int start, int end); + + } // class NameIterator + +} diff --git a/mirai-console/backend/mirai-console/src/data/PluginDataStorage.kt b/mirai-console/backend/mirai-console/src/data/PluginDataStorage.kt index a4a6321b2a..1c0c1aa9e2 100644 --- a/mirai-console/backend/mirai-console/src/data/PluginDataStorage.kt +++ b/mirai-console/backend/mirai-console/src/data/PluginDataStorage.kt @@ -17,7 +17,7 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import java.io.File -import java.nio.file.Path +import com.llamalab.safs.Path /** * [数据对象][PluginData] 存储仓库. diff --git a/mirai-console/backend/mirai-console/src/internal/auth/ConsoleSecretsCalculator.kt b/mirai-console/backend/mirai-console/src/internal/auth/ConsoleSecretsCalculator.kt index 73f0395c99..5685c935f0 100644 --- a/mirai-console/backend/mirai-console/src/internal/auth/ConsoleSecretsCalculator.kt +++ b/mirai-console/backend/mirai-console/src/internal/auth/ConsoleSecretsCalculator.kt @@ -13,12 +13,13 @@ import net.mamoe.mirai.utils.SecretsProtection import net.mamoe.mirai.utils.lateinitMutableProperty import java.io.ByteArrayOutputStream import java.io.DataOutputStream -import java.nio.file.Path +import com.llamalab.safs.Path import java.util.* -import kotlin.io.path.createDirectories -import kotlin.io.path.isRegularFile -import kotlin.io.path.readBytes -import kotlin.io.path.writeBytes +import com.llamalab.safs.kotlin.io.path.createDirectories +import com.llamalab.safs.kotlin.io.path.readBytes +import com.llamalab.safs.kotlin.io.path.writeBytes + +import com.llamalab.safs.kotlin.io.path.isRegularFile internal class ConsoleSecretsCalculator( private val file: Path, diff --git a/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt index 4048c24391..be5cb0410d 100644 --- a/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt @@ -24,7 +24,7 @@ import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.yamlkt.Yaml import java.io.File -import java.nio.file.Path +import com.llamalab.safs.Path @Suppress("RedundantVisibilityModifier") // might be public in the future internal open class MultiFilePluginDataStorageImpl( diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt b/mirai-console/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt index 15ac1bad39..ce5cab09e2 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt @@ -31,7 +31,7 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.* import net.mamoe.yamlkt.Yaml import java.io.File -import java.nio.file.Path +import com.llamalab.safs.Path import java.util.concurrent.ConcurrentHashMap import kotlin.coroutines.CoroutineContext diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt index e4d76fee60..6b62a8631d 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt @@ -41,7 +41,7 @@ import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.safeCast import java.io.File import java.io.InputStream -import java.nio.file.Path +import com.llamalab.safs.Path import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.coroutines.CoroutineContext diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/NotYetLoadedJvmPlugin.kt b/mirai-console/backend/mirai-console/src/internal/plugin/NotYetLoadedJvmPlugin.kt index aae67fb745..9f743f828d 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/NotYetLoadedJvmPlugin.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/NotYetLoadedJvmPlugin.kt @@ -19,7 +19,7 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.utils.MiraiLogger import java.io.File import java.io.InputStream -import java.nio.file.Path +import com.llamalab.safs.Path import kotlin.coroutines.CoroutineContext internal abstract class NotYetLoadedJvmPlugin( diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt b/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt index 0cb215d6ea..cd7471bd58 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt @@ -30,7 +30,7 @@ import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.* import java.io.File -import java.nio.file.Path +import com.llamalab.safs.Path import java.util.concurrent.CopyOnWriteArrayList import kotlin.coroutines.CoroutineContext diff --git a/mirai-console/backend/mirai-console/src/internal/shutdown/ShutdownDaemon.kt b/mirai-console/backend/mirai-console/src/internal/shutdown/ShutdownDaemon.kt index 24d9ec7c72..a4a3e471b6 100644 --- a/mirai-console/backend/mirai-console/src/internal/shutdown/ShutdownDaemon.kt +++ b/mirai-console/backend/mirai-console/src/internal/shutdown/ShutdownDaemon.kt @@ -24,7 +24,7 @@ import java.io.FileOutputStream import java.io.PrintStream import java.lang.management.ManagementFactory import java.lang.reflect.Method -import java.nio.file.Paths +import com.llamalab.safs.Paths import java.time.Instant import java.time.ZoneOffset import java.util.* @@ -33,7 +33,7 @@ import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger -import kotlin.io.path.writeText +import com.llamalab.safs.kotlin.io.path.writeText internal object ShutdownDaemon { @Suppress("RemoveRedundantQualifierName") @@ -110,6 +110,7 @@ internal object ShutdownDaemon { bridge.mainLogger.debug { "SHUTDOWN DAEMON STARTED........." } } + @OptIn(ConsoleInternalApi::class) @Suppress("MemberVisibilityCanBePrivate") fun dumpCrashReport(saveError: Boolean) { val isAndroidSystem = kotlin.runCatching { Class.forName("android.util.Log") }.isSuccess diff --git a/mirai-console/backend/mirai-console/src/plugin/PluginFileExtensions.kt b/mirai-console/backend/mirai-console/src/plugin/PluginFileExtensions.kt index a4e134e5d1..338a775d93 100644 --- a/mirai-console/backend/mirai-console/src/plugin/PluginFileExtensions.kt +++ b/mirai-console/backend/mirai-console/src/plugin/PluginFileExtensions.kt @@ -13,7 +13,7 @@ import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import java.io.File -import java.nio.file.Path +import com.llamalab.safs.Path /** diff --git a/mirai-console/backend/mirai-console/src/plugin/PluginManager.kt b/mirai-console/backend/mirai-console/src/plugin/PluginManager.kt index 1207fffa97..fb7c760bd7 100644 --- a/mirai-console/backend/mirai-console/src/plugin/PluginManager.kt +++ b/mirai-console/backend/mirai-console/src/plugin/PluginManager.kt @@ -17,7 +17,7 @@ import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.utils.NotStableForInheritance import java.io.File -import java.nio.file.Path +import com.llamalab.safs.Path /** * 插件管理器.