package com.beijunyi.parallelgit.filesystem;
import java.io.IOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.nio.file.spi.FileSystemProvider;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.beijunyi.parallelgit.filesystem.io.*;
import com.beijunyi.parallelgit.filesystem.utils.GfsConfiguration;
import com.beijunyi.parallelgit.filesystem.utils.GfsUriUtils;
import static java.nio.file.StandardOpenOption.*;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static java.util.UUID.randomUUID;
public class GitFileSystemProvider extends FileSystemProvider {
public static final String GFS = "gfs";
public static final String BRANCH = "branch";
public static final String COMMIT = "commit";
public static final Collection<OpenOption> SUPPORTED_OPEN_OPTIONS = supportedOpenOption();
private static final GitFileSystemProvider INSTANCE = getInstalledProvider();
private static final Map<String, GitFileSystem> FILE_SYSTEMS = new ConcurrentHashMap<>();
@Nonnull
public static GitFileSystemProvider getDefault() {
return INSTANCE;
}
@Nonnull
@Override
public String getScheme() {
return GFS;
}
@Nonnull
@Override
public GitFileSystem newFileSystem(Path path, Map<String, ?> properties) throws IOException {
return newFileSystem(GfsConfiguration.fromPath(path, properties));
}
@Nonnull
@Override
public GitFileSystem newFileSystem(URI uri, Map<String, ?> properties) throws IOException {
return newFileSystem(GfsConfiguration.fromUri(uri, properties));
}
@Nonnull
public GitFileSystem newFileSystem(GfsConfiguration cfg) throws IOException {
String sid = randomUUID().toString();
GitFileSystem ret = new GitFileSystem(cfg, sid);
FILE_SYSTEMS.put(sid, ret);
return ret;
}
public void unregister(GitFileSystem gfs) {
FILE_SYSTEMS.remove(gfs.getSessionId());
}
@Nonnull
@Override
public GitFileSystem getFileSystem(URI uri) {
String session = GfsUriUtils.getSession(uri);
if(session == null)
throw new FileSystemNotFoundException();
return getFileSystem(session);
}
@Nonnull
public GitFileSystem getFileSystem(String sid) {
GitFileSystem ret = FILE_SYSTEMS.get(sid);
if(ret == null)
throw new FileSystemNotFoundException(sid);
return ret;
}
@Nonnull
@Override
public GitPath getPath(URI uri) throws FileSystemNotFoundException {
GitFileSystem gfs = getFileSystem(uri);
String file = GfsUriUtils.getFile(uri);
return gfs.getPath(file).toRealPath();
}
@Nonnull
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException, UnsupportedOperationException {
Set<OpenOption> amended = new HashSet<>();
for(OpenOption option : options) {
if(!SUPPORTED_OPEN_OPTIONS.contains(option)) throw new UnsupportedOperationException(option.toString());
if(option == APPEND) amended.add(WRITE);
amended.add(option);
}
if(!amended.contains(WRITE)) amended.add(READ);
return GfsIO.newByteChannel(((GitPath)path).toRealPath(), amended, asList(attrs));
}
@Nonnull
@Override
public GfsDirectoryStream newDirectoryStream(Path path, @Nullable DirectoryStream.Filter<? super Path> filter) throws IOException {
return GfsIO.newDirectoryStream(((GitPath)path).toRealPath(), filter);
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
GfsIO.createDirectory((GitPath)dir);
}
@Override
public void delete(Path path) throws IOException {
GfsIO.delete(((GitPath)path).toRealPath());
}
@Override
public void copy(Path source, Path target, CopyOption... options) throws IOException {
GfsIO.copy((GitPath)source, (GitPath)target, new HashSet<>(asList(options)));
}
@Override
public void move(Path source, Path target, CopyOption... options) throws IOException {
GfsIO.move((GitPath)source, (GitPath)target, new HashSet<>(asList(options)));
}
@Override
public boolean isSameFile(Path path, Path path2) {
GitPath p1 = ((GitPath) path).toRealPath();
GitPath p2 = ((GitPath) path).toRealPath();
return Objects.equals(p1, p2);
}
@Override
public boolean isHidden(Path path) {
GitPath filename = ((GitPath) path).toRealPath().getFileName();
return filename != null && filename.toString().charAt(0) == '.';
}
@Nonnull
@Override
public GfsFileStore getFileStore(Path path) {
return ((GitPath) path).getFileStore();
}
@Override
public void checkAccess(Path path, AccessMode... modes) throws IOException {
GfsIO.checkAccess(((GitPath)path).toRealPath(), new HashSet<>(asList(modes)));
}
@Nullable
@Override
public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) throws UnsupportedOperationException {
try {
return GfsIO.getFileAttributeView(((GitPath)path).toRealPath(), type);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
@Nonnull
@Override
public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
Class<? extends BasicFileAttributeView> viewType;
if(type.isAssignableFrom(GfsFileAttributes.Basic.class)) {
viewType = GfsFileAttributeView.Basic.class;
} else if(type.isAssignableFrom(GfsFileAttributes.Posix.class)) {
viewType = GfsFileAttributeView.Posix.class;
} else if(type.isAssignableFrom(GfsFileAttributes.Git.class)) {
viewType = GfsFileAttributeView.Git.class;
} else {
throw new UnsupportedOperationException(type.getName());
}
BasicFileAttributeView view = getFileAttributeView(path, viewType, options);
if(view == null)
throw new NoSuchFileException(path.toString());
return type.cast(view.readAttributes());
}
@Nonnull
@Override
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
int viewNameEnd = attributes.indexOf(':');
String viewName = viewNameEnd >= 0 ? attributes.substring(0, viewNameEnd) : GfsFileAttributeView.Basic.BASIC_VIEW;
String keys = viewNameEnd >= 0 ? attributes.substring(viewNameEnd + 1) : attributes;
Class<? extends GfsFileAttributeView> viewType;
switch(viewName) {
case GfsFileAttributeView.Basic.BASIC_VIEW:
viewType = GfsFileAttributeView.Basic.class;
break;
case GfsFileAttributeView.Posix.POSIX_VIEW:
viewType = GfsFileAttributeView.Posix.class;
break;
case GfsFileAttributeView.Git.GIT_VIEW:
viewType = GfsFileAttributeView.Git.class;
break;
default:
throw new UnsupportedOperationException("View \"" + viewName + "\" is not available");
}
GfsFileAttributeView view = getFileAttributeView(path, viewType, options);
if(view == null)
throw new NoSuchFileException(path.toString());
return view.readAttributes(asList(keys.split(",")));
}
@Override
public void setAttribute(@Nullable Path path, @Nullable String attribute, @Nullable Object value, @Nullable LinkOption... options) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Nonnull
private static GitFileSystemProvider getInstalledProvider() {
GitFileSystemProvider ret = null;
for(FileSystemProvider provider : FileSystemProvider.installedProviders()) {
if(provider instanceof GitFileSystemProvider) {
ret = (GitFileSystemProvider) provider;
break;
}
}
if(ret == null)
ret = new GitFileSystemProvider();
return ret;
}
@Nonnull
private static Collection<OpenOption> supportedOpenOption() {
List<OpenOption> options = Arrays.<OpenOption>asList(READ, SPARSE, CREATE, CREATE_NEW, WRITE, APPEND, TRUNCATE_EXISTING);
return unmodifiableList(options);
}
}