如何在JFileChooser中导航到网络主机?

12

问题

我有一个JFileChooser,需要以编程方式将其currentDirectory设置为包含多个SMB共享的网络主机(例如\\blah)。从技术上讲,这不是一个“目录”,而是代表可用共享列表的shell文件夹。

  • JFileChooser可以轻松导航到特定共享(例如\\blah\someShare),但无法处理主机“目录”本身(例如\\blah)。

  • 用户可以通过经过“网络”shell文件夹或查找特定共享并导航到其父目录来在JFileChooser中导航到此类“目录”。调试显示,在幕后,此目录表示为Win32ShellFolder2。到目前为止,我所有尝试的编程设置currentDirectory都失败了。

  • new File("\\\\blah")可以创建,但是Java无法确定其实际是否存在。

尝试失败的解决方案

  • chooser.setCurrentDirectory(new File("\\\\blah"));

    失败,因为JFileChooser检查给定的目录是否存在,而new File("\\\\blah").exists()返回false。

  • File dir = new File("\\\\blah").getCanonicalFile();

    失败并引发异常:

  •   java.io.IOException: Invalid argument
      at java.io.WinNTFileSystem.canonicalize0(Native Method)
      at java.io.WinNTFileSystem.canonicalize(WinNTFileSystem.java:428)
      at java.io.File.getCanonicalPath(File.java:618)
      at java.io.File.getCanonicalFile(File.java:643)
    
  • File dir = ShellFolder.getShellFolder(new File("\\\\blah"));

    执行时会抛出异常:

  •   java.io.FileNotFoundException
      at sun.awt.shell.ShellFolder.getShellFolder(ShellFolder.java:247)
    
  • File dir = new Win32ShellFolderManager2().createShellFolder(new File("\\\\blah"));

    执行时抛出了异常:

  •   java.io.FileNotFoundException: File \\blah not found
      at sun.awt.shell.Win32ShellFolderManager2.createShellFolder(Win32ShellFolderManager2.java:80)
      at sun.awt.shell.Win32ShellFolderManager2.createShellFolder(Win32ShellFolderManager2.java:64)
    
  • Path dir = Paths.get("\\\\blah");

    会抛出一个异常:

  • java.nio.file.InvalidPathException: UNC path is missing sharename: \\blah
    at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:118)
    at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
    at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94)
    at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255)
    at java.nio.file.Paths.get(Paths.java:84)
    

以下解决方案似乎不适用于Java 8+。我建议我的用户在桌面上创建一个指向他们服务器的快捷方式。然后,他们只需在JFileChooser中按下主页按钮并双击服务器快捷方式,即可访问所有共享文件夹。 - Kevin Rahe
2个回答

6
从前,我曾经遇到过这样的任务,我可以说它真的很烦人。一开始听起来很简单,但是当你开始挖掘和尝试时,会出现越来越多的问题。我想谈谈我的旅程。
据我所了解,这里的问题在于 \\ComputerName\ 不是文件系统中的一个真实位置。它是一个抽象层,其内容取决于您的身份验证凭据。而且它只适用于 Windows 机器,因此前往那里将违反 Java 的系统独立性法则。总结一下,这不是一个 File 对象可以指向的东西。 您可以使用 Samba 库 jcifs,但在他们的实现中,SmbFile 类需要用户身份验证,并且与 java File 类不兼容。因此,您无法将其与 jFileChooser 一起使用。不幸的是,他们对更改它没有兴趣,您可以在这里阅读到相关信息。
我自己尝试开发一个文件包装器,作为 FileSmbFile 类的混合体。但是我放弃了,因为它给我带来了噩梦。
然后我想到了编写一个简单的对话框,列出之前使用 jcifs 扫描过的网络共享,并让用户选择其中之一。然后应该会显示一个带有所选共享的 jFileChooser

在我实现这个想法的时候,整个问题的超级简单解决方案蹦进了我的脑海。

由于指向 \\ComputerName\ShareName 并单击 上一级 按钮绝对不是问题,因此必须可以重现此步骤。而且确实如此。实际上,在查看 jFileChooser 的内部工作原理时,我了解到像 MyComputerNetwork 这样的位置是 ShellFolders,它们是 File 对象的特殊情况。但是这些 Shell 文件夹受到保护,不属于 Java API 的一部分。
所以我无法直接实例化它们。但是我可以访问处理文件系统上的系统相关视图的 FileSystemView,例如为特殊位置创建这些 Shell 文件夹。
长话短说。如果您知道一个共享名称,请创建到此共享名称的文件。然后使用 FileSystemView 获取其父文件。然后,您就可以使用结果为 jFileChooser 扩展了 ShellFolderFile 对象。
File f = new File("\\\\ComputerName\\ShareFolder");
FileSystemView fsv = FileSystemView.getFileSystemView();
f = fsv.getParentDirectory(f);
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(f);

最后需要注意的是:这个解决方案不会要求您提供登录信息,所以在使用此方法之前必须已经在Windows中获得对共享文件夹的访问权限。

编辑:很抱歉文字有点长,当时是元旦狂欢喝醉了。现在我想补充一下,其它方向也可以实现相同的效果。

FileSystemView fsv = FileSystemView.getFileSystemView();
File Desktop = fsv.getRoots()[0];

在 Windows 系统上,这应该会给你桌面文件夹。如果您在此处列出所有文件:
for(File file : Desktop.listFiles())
    System.out.println(file.getName());

你会注意到一些具有奇怪名称的条目:
::{20D04FE0-3AEA-1069-A2D8-08002B30309D}   // My Computer
::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}   // Network
::{031E4825-7B94-4DC3-B131-E946B44C8DD5}   // User Directory

我不确定这些代码是否适用于所有Windows版本,但似乎适用于Windows7。因此,您可以使用此代码获取网络Shell文件夹,然后再获取共享计算机。

File Network = fsv.getChild(Desktop, "::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}");
File Host = fsv.getChild(Network, "COMPUTERNAME");  // Must be in Capital Letters

这里的问题在于,这将需要约10秒钟的时间,因为正在扫描网络文件夹的内容。

感谢您的调查和分享这些想法。不幸的是,对于我的用例,这两个提案都不适用,因为我们的代码没有关于给定计算机节点上任何共享的预先知识,并且给定节点可能不会列在网络shell文件夹下。幸运的是,找到了一个更通用的解决方案,适用于我:https://dev59.com/zVwX5IYBdhLWcg3wlgTl#34620383 - matvei

3
我找到了一个针对Windows的解决方案,可以仅通过名称(例如\\blah\\blah\)导航到任何可访问的计算机节点,而无需枚举网络Shell文件夹或具有关于给定节点上网络共享的任何先前知识。
创建计算机路径的ShellFolder 在调试此问题时,我发现必须为给定的计算机路径创建一个ShellFolder才能导航到它。 Win32ShellFolderManager2.createShellFolder()将在给定的文件上调用File.getCanonicalPath(),后者将依次调用WinNTFileSystem.canonicalize()。这最后一步在计算机路径上总是失败的。经过多次尝试,我成功地为任何可访问的计算机路径创建了一个ShellFolder,方法是将文件对象包装在绕过WinNTFileSystem.canonicalize()的东西中:
/**
 * Create a shell folder for a given network path.
 *
 * @param path File to test for existence.
 * @return ShellFolder representing the given computer node.
 * @throws IllegalArgumentException given path is not a computer node.
 * @throws FileNotFoundException given path could not be found.
 */
public static ShellFolder getComputerNodeFolder(String path)
        throws FileNotFoundException {
    File file = new NonCanonicalizingFile(path);
    if (ShellFolder.isComputerNode(file)) {
        return new Win32ShellFolderManager2().createShellFolder(file);
    } else {
        throw new IllegalArgumentException("Given path is not a computer node.");
    }
}

private static final class NonCanonicalizingFile extends File {
    public NonCanonicalizingFile(String path) {
        super(path);
    }

    @Override
    public String getCanonicalPath() throws IOException {
        // Win32ShellFolderManager2.createShellFolder() will call getCanonicalPath() on this file.
        // Base implementation of getCanonicalPath() calls WinNTFileSystem.canonicalize() which fails on
        // computer nodes (e.g. "\\blah"). We skip the canonicalize call, which is safe at this point because we've
        // confirmed (in approveSelection()) that this file represents a computer node.
        return getAbsolutePath();
    }
}

不可否认,这个解决方案有一些边缘情况(例如,\\blah\可以工作,但\\blah\someShare\..\就不行),理想情况下,OpenJDK应该在他们的端口修复这些怪癖。这也是一个特定于操作系统和实现的解决方案,在OpenJDK-on-Windows设置之外将无法工作。

与JFileChooser集成:选项1

将其与JFileChooser集成的最简单方法是覆盖其approveSelection()方法。这允许用户在对话框中键入计算机路径(\\blah\\blah\)并按Enter导航到该位置。当给出不存在或不可访问的路径时,会显示警报消息。

JFileChooser chooser = new JFileChooser() {
    @Override
    public void approveSelection() {
        File selectedFile = getSelectedFile();
        if (selectedFile != null && ShellFolder.isComputerNode(selectedFile)) {
            try {
                // Resolve path and try to navigate to it
                setCurrentDirectory(getComputerNodeFolder(selectedFile.getPath()));
            } catch (FileNotFoundException ex) {
                // Alert user if given computer node cannot be accessed
                JOptionPane.showMessageDialog(this, "Cannot access " + selectedFile.getPath());
            }
        } else {
            super.approveSelection();
        }
    }
};
chooser.showOpenDialog(null);

与JFileChooser集成:选项2

另外,可以通过覆盖FileSystemViewcreateFileObject(String)方法来增强它,以检查计算机路径。这允许将计算机路径传递给JFileChooser(String,FileSystemView)构造函数,并仍然允许用户导航到可访问的计算机路径。但是,仍然没有简单的方法向用户发送有关不可访问计算机路径的消息,而无需覆盖JFileChooser.approveSelection()

public static class ComputerNodeFriendlyFileSystemView extends FileSystemView {

    private final FileSystemView delegate;

    public ComputerNodeFriendlyFileSystemView(FileSystemView delegate) {
        this.delegate = delegate;
    }

    @Override
    public File createFileObject(String path) {
        File placeholderFile = new File(path);
        if (ShellFolder.isComputerNode(placeholderFile)) {
            try {
                return getComputerNodeFolder(path);
            } catch (FileNotFoundException ex) {
                return placeholderFile;
            }
        } else {
            return delegate.createFileObject(path);
        }
    }

    // All code below simply delegates everything to the "delegate"

    @Override
    public File createNewFolder(File containingDir) throws IOException {
        return delegate.createNewFolder(containingDir);
    }

    @Override
    public boolean isRoot(File f) {
        return delegate.isRoot(f);
    }

    @Override
    public Boolean isTraversable(File f) {
        return delegate.isTraversable(f);
    }

    @Override
    public String getSystemDisplayName(File f) {
        return delegate.getSystemDisplayName(f);
    }

    @Override
    public String getSystemTypeDescription(File f) {
        return delegate.getSystemTypeDescription(f);
    }

    @Override
    public Icon getSystemIcon(File f) {
        return delegate.getSystemIcon(f);
    }

    @Override
    public boolean isParent(File folder, File file) {
        return delegate.isParent(folder, file);
    }

    @Override
    public File getChild(File parent, String fileName) {
        return delegate.getChild(parent, fileName);
    }

    @Override
    public boolean isFileSystem(File f) {
        return delegate.isFileSystem(f);
    }

    @Override
    public boolean isHiddenFile(File f) {
        return delegate.isHiddenFile(f);
    }

    @Override
    public boolean isFileSystemRoot(File dir) {
        return delegate.isFileSystemRoot(dir);
    }

    @Override
    public boolean isDrive(File dir) {
        return delegate.isDrive(dir);
    }

    @Override
    public boolean isFloppyDrive(File dir) {
        return delegate.isFloppyDrive(dir);
    }

    @Override
    public boolean isComputerNode(File dir) {
        return delegate.isComputerNode(dir);
    }

    @Override
    public File[] getRoots() {
        return delegate.getRoots();
    }

    @Override
    public File getHomeDirectory() {
        return delegate.getHomeDirectory();
    }

    @Override
    public File getDefaultDirectory() {
        return delegate.getDefaultDirectory();
    }

    @Override
    public File createFileObject(File dir, String filename) {
        return delegate.createFileObject(dir, filename);
    }

    @Override
    public File[] getFiles(File dir, boolean useFileHiding) {
        return delegate.getFiles(dir, useFileHiding);
    }

    @Override
    public File getParentDirectory(File dir) {
        return delegate.getParentDirectory(dir);
    }
}

使用方法:

ComputerNodeFriendlyFileSystemView fsv
    = new ComputerNodeFriendlyFileSystemView(FileSystemView.getFileSystemView());
JFileChooser chooser = new JFileChooser("\\\\blah", fsv);
chooser.showOpenDialog(null);

在Java 8中,“Win32ShellFolderManager2”似乎不再是Java的一部分。 - Kevin Rahe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接