为了学习的目的,我正在尝试开发一些东西,它允许我将任何类型的网络资源(如 HTTP/FTP 目录和类似的东西)挂载为本地文件夹。
我在这个库中使用 FUSE + JAVA:https ://github.com/EtiennePerot/fuse-jna
我之所以选择 JAVA,是因为我需要将它作为 Web 服务工作,而我只知道如何使用 JAVA 来实现。
现在,在这个简短的介绍之后,这是我的场景:
_______________ ____________ __________
| | | | | |
|remote resource|<--HTTP/FTP-->|fuse machine|<--SAMBA-->|desktop pc|
|_______________| |____________| |__________|
我从这个例子开始:
package net.fusejna.examples;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import net.fusejna.DirectoryFiller;
import net.fusejna.ErrorCodes;
import net.fusejna.StructFuseFileInfo.FileInfoWrapper;
import net.fusejna.StructStat.StatWrapper;
import net.fusejna.types.TypeMode.ModeWrapper;
import net.fusejna.types.TypeMode.NodeType;
import net.fusejna.util.FuseFilesystemAdapterAssumeImplemented;
public final class MemoryFS extends FuseFilesystemAdapterAssumeImplemented
{
private final class MemoryDirectory extends MemoryPath
{
private final List<MemoryPath> contents = new ArrayList<MemoryPath>();
private MemoryDirectory(final String name, final MemoryDirectory parent)
{
super(name, parent);
}
public void add(final MemoryPath p)
{
contents.add(p);
p.parent = this;
}
@Override
protected MemoryPath find(String path)
{
if (super.find(path) != null) {
return super.find(path);
}
while (path.startsWith("/")) {
path = path.substring(1);
}
if (!path.contains("/")) {
for (final MemoryPath p : contents) {
if (p.name.equals(path)) {
return p;
}
}
return null;
}
final String nextName = path.substring(0, path.indexOf("/"));
final String rest = path.substring(path.indexOf("/"));
for (final MemoryPath p : contents) {
if (p.name.equals(nextName)) {
return p.find(rest);
}
}
return null;
}
@Override
protected void getattr(final StatWrapper stat)
{
stat.setMode(NodeType.DIRECTORY);
}
private void mkdir(final String lastComponent)
{
contents.add(new MemoryDirectory(lastComponent, this));
}
public void mkfile(final String lastComponent)
{
contents.add(new MemoryFile(lastComponent, this));
}
public void read(final DirectoryFiller filler)
{
for (final MemoryPath p : contents) {
filler.add(p.name);
}
}
}
private final class MemoryFile extends MemoryPath
{
private ByteBuffer contents = ByteBuffer.allocate(0);
private MemoryFile(final String name, final MemoryDirectory parent)
{
super(name, parent);
}
@Override
protected void getattr(final StatWrapper stat)
{
stat.setMode(NodeType.FILE);
stat.size(contents.capacity());
}
private int read(final ByteBuffer buffer, final long size, final long offset)
{
final int bytesToRead = (int) Math.min(contents.capacity() - offset, size);
final byte[] bytesRead = new byte[bytesToRead];
contents.get(bytesRead, (int) offset, bytesToRead);
buffer.put(bytesRead);
contents.position(0); // Rewind
return bytesToRead;
}
private void truncate(final long size)
{
if (size < contents.capacity()) {
// Need to create a new, smaller buffer
final ByteBuffer newContents = ByteBuffer.allocate((int) size);
final byte[] bytesRead = new byte[(int) size];
contents.get(bytesRead);
newContents.put(bytesRead);
contents = newContents;
}
}
private int write(final ByteBuffer buffer, final long bufSize, final long writeOffset)
{
final int maxWriteIndex = (int) (writeOffset + bufSize);
if (maxWriteIndex > contents.capacity()) {
// Need to create a new, larger buffer
final ByteBuffer newContents = ByteBuffer.allocate(maxWriteIndex);
newContents.put(contents);
contents = newContents;
}
final byte[] bytesToWrite = new byte[(int) bufSize];
buffer.get(bytesToWrite, 0, (int) bufSize);
contents.position((int) writeOffset);
contents.put(bytesToWrite);
contents.position(0); // Rewind
return (int) bufSize;
}
}
private abstract class MemoryPath
{
private final String name;
private MemoryDirectory parent;
private MemoryPath(final String name, final MemoryDirectory parent)
{
this.name = name;
this.parent = parent;
}
private void delete()
{
if (parent != null) {
parent.contents.remove(this);
parent = null;
}
}
protected MemoryPath find(String path)
{
while (path.startsWith("/")) {
path = path.substring(1);
}
if (path.equals(name) || path.isEmpty()) {
return this;
}
return null;
}
protected abstract void getattr(StatWrapper stat);
}
public static void main(final String... args)
{
if (args.length != 1) {
System.err.println("Usage: MemoryFS <mountpoint>");
System.exit(1);
}
try {
new MemoryFS().log(true).mount(args[0]);
}
catch (final Throwable e) {
System.err.println(e);
}
}
private final MemoryDirectory rootDirectory = new MemoryDirectory("", null);
public MemoryFS()
{
// Nothing
}
@Override
public int access(final String path, final int access)
{
return 0;
}
@Override
public int create(final String path, final ModeWrapper mode, final FileInfoWrapper info)
{
if (getPath(path) != null) {
return -ErrorCodes.EEXIST;
}
final MemoryPath parent = getParentPath(path);
if (parent instanceof MemoryDirectory) {
((MemoryDirectory) parent).mkfile(getLastComponent(path));
return 0;
}
return -ErrorCodes.ENOENT;
}
@Override
public int getattr(final String path, final StatWrapper stat)
{
final MemoryPath p = getPath(path);
if (p != null) {
p.getattr(stat);
return 0;
}
return -ErrorCodes.ENOENT;
}
private String getLastComponent(String path)
{
while (path.substring(path.length() - 1).equals("/")) {
path = path.substring(0, path.length() - 1);
}
if (path.isEmpty()) {
return "";
}
return path.substring(path.lastIndexOf("/") + 1);
}
private MemoryPath getParentPath(final String path)
{
return rootDirectory.find(path.substring(0, path.lastIndexOf("/")));
}
private MemoryPath getPath(final String path)
{
return rootDirectory.find(path);
}
@Override
public int mkdir(final String path, final ModeWrapper mode)
{
if (getPath(path) != null) {
return -ErrorCodes.EEXIST;
}
final MemoryPath parent = getParentPath(path);
if (parent instanceof MemoryDirectory) {
((MemoryDirectory) parent).mkdir(getLastComponent(path));
return 0;
}
return -ErrorCodes.ENOENT;
}
@Override
public int open(final String path, final FileInfoWrapper info)
{
return 0;
}
@Override
public int read(final String path, final ByteBuffer buffer, final long size, final long offset, final FileInfoWrapper info)
{
final MemoryPath p = getPath(path);
if (p == null) {
return -ErrorCodes.ENOENT;
}
if (!(p instanceof MemoryFile)) {
return -ErrorCodes.EISDIR;
}
return ((MemoryFile) p).read(buffer, size, offset);
}
@Override
public int readdir(final String path, final DirectoryFiller filler)
{
final MemoryPath p = getPath(path);
if (p == null) {
return -ErrorCodes.ENOENT;
}
if (!(p instanceof MemoryDirectory)) {
return -ErrorCodes.ENOTDIR;
}
((MemoryDirectory) p).read(filler);
return 0;
}
@Override
public int rename(final String path, final String newName)
{
final MemoryPath p = getPath(path);
if (p == null) {
return -ErrorCodes.ENOENT;
}
final MemoryPath newParent = getParentPath(newName);
if (newParent != null) {
return -ErrorCodes.ENOENT;
}
if (!(newParent instanceof MemoryDirectory)) {
return -ErrorCodes.ENOTDIR;
}
p.delete();
((MemoryDirectory) newParent).add(p);
return 0;
}
@Override
public int rmdir(final String path)
{
final MemoryPath p = getPath(path);
if (p == null) {
return -ErrorCodes.ENOENT;
}
if (!(p instanceof MemoryDirectory)) {
return -ErrorCodes.ENOTDIR;
}
p.delete();
return 0;
}
@Override
public int truncate(final String path, final long offset)
{
final MemoryPath p = getPath(path);
if (p == null) {
return -ErrorCodes.ENOENT;
}
if (!(p instanceof MemoryFile)) {
return -ErrorCodes.EISDIR;
}
((MemoryFile) p).truncate(offset);
return 0;
}
@Override
public int unlink(final String path)
{
final MemoryPath p = getPath(path);
if (p == null) {
return -ErrorCodes.ENOENT;
}
p.delete();
return 0;
}
@Override
public int write(final String path, final ByteBuffer buf, final long bufSize, final long writeOffset,
final FileInfoWrapper wrapper)
{
final MemoryPath p = getPath(path);
if (p == null) {
return -ErrorCodes.ENOENT;
}
if (!(p instanceof MemoryFile)) {
return -ErrorCodes.EISDIR;
}
return ((MemoryFile) p).write(buf, bufSize, writeOffset);
}
}
但是问题来了。
如果我没记错的话,FUSE 会在列出目录时加载文件内容。
这意味着即使用户没有打开文件,也必须完全加载文件。
所以第一个问题是:如何在不使用代理模式的情况下“按需”加载文件?(谁将整个文件系统加载到内存中??)
第一个问题是远程资源和熔断器之间的问题,第二个问题向前迈进了一步,涉及熔断器和台式电脑。
由于我说的是远程资源,这意味着熔断器必须先下载它们,然后才能与台式机共享它们。当然,这将导致同一资源的 2 次下载,一次由 fuse 启动,另一次由台式机启动。
这是第二个问题:有没有办法让我的保险丝机器在打开文件时返回类似于桌面电脑的链接?(这样桌面电脑要求一个文件,保险丝机器给它一个“链接”,它开始直接从远程资源下载它,避免了两个下载之一)。
我知道文字有点混乱,但我在这个问题上比文字本身更困惑,所以如果我无法更好地解释我需要什么,我深表歉意,但我真的不知道如何去问吧!