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

mat*_*vei 11 java windows filesystems smb jfilechooser

问题

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

  • JFileChooser在导航到特定共享(例如\\blah\someShare)时没有问题,但是无法处理主机"目录"本身(例如\\blah).

  • 用户可以通过"网络"shell文件夹,或通过查找特定共享并导航到其父目录,导航到JFileChooser内的此类"目录".调试显示引擎盖下此目录表示为a 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)
    
    Run Code Online (Sandbox Code Playgroud)
  • File dir = ShellFolder.getShellFolder(new File("\\\\blah"));

    失败,但有异常:

      java.io.FileNotFoundException
      at sun.awt.shell.ShellFolder.getShellFolder(ShellFolder.java:247)
    
    Run Code Online (Sandbox Code Playgroud)
  • 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)
    
    Run Code Online (Sandbox Code Playgroud)
  • 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)
    
    Run Code Online (Sandbox Code Playgroud)

Arc*_*ord 5

曾几何时,我遇到过这样的任务,我可以说它真的很烦人.首先它听起来很容易,但是当你开始挖掘和尝试时,会出现越来越多的问题.我想谈谈我的旅程.
根据我的理解,这里的事情是\\ComputerName\在文件系统中不是一个真正的地方.它是一个抽象层,其内容取决于您的身份验证凭据.它只适用于Windows机器,因此会破坏Java的系统独立性定律.总结一下,这不是一个File对象可以指出的.您可以使用Samba库jcifs,但在其实现中,该类SmbFile需要用户身份验证,并且与java File类不兼容.所以你不能用它jFileChooser.可悲的是,他们并没有因为你可以在这里阅读而改变它.
我尝试自己开发一个File包装器,它充当了Class FileSmbFileClass 的混合体.但是我放弃了,因为它给我带来了噩梦.
然后我想到了编写一个简单的Dialog,它列出了以前扫描过的网络共享,jcifs并让用户选择其中一个.然后jFileChooser应显示带有所选共享的a .

当我实现这个想法时,对整个问题的超级简单解决方案踢了我的屁股.

由于指向\\ComputerName\ShareName并单击One level higher按钮绝对没有问题,因此必须能够重现此步骤.它是.其实,同时引擎盖下看jFileChooser我了解到,像的地方MyComputer或者NetworkShellFolders它们的特例File对象.但是这些Shell文件夹受到保护,而不是Java API的一部分.
所以我无法直接实例化这些.但我可以访问FileSystemView处理文件系统上依赖于系统的视图,例如为特殊位置创建这些Shell文件夹.
如此长的文字简短的回答.如果您知道一个Sharename,请为此共享名称创建一个文件.然后使用FileSystemView获取其父文件.瞧,你可以使用所产生的File,其延伸的对象ShellFolderjFileChooser.

File f = new File("\\\\ComputerName\\ShareFolder");
FileSystemView fsv = FileSystemView.getFileSystemView();
f = fsv.getParentDirectory(f);
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(f);
Run Code Online (Sandbox Code Playgroud)

最后一点:此解决方案不会要求您提供登录信息.因此,在此处使用之前,必须先在Windows中访问共享.

编辑:抱歉长文.新年前夜和我喝醉了.现在我想补充一点,我发现了另一种方式.

FileSystemView fsv = FileSystemView.getFileSystemView();
File Desktop = fsv.getRoots()[0];
Run Code Online (Sandbox Code Playgroud)

在Windows系统上,这应该为您提供桌面文件夹.如果你在这里列出所有文件:

for(File file : Desktop.listFiles())
    System.out.println(file.getName());
Run Code Online (Sandbox Code Playgroud)

您会注意到一些奇怪名称的条目:

::{20D04FE0-3AEA-1069-A2D8-08002B30309D}   // My Computer
::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}   // Network
::{031E4825-7B94-4DC3-B131-E946B44C8DD5}   // User Directory
Run Code Online (Sandbox Code Playgroud)

我不知道这些代码对于所有Windows版本是否相同,但似乎它们适用于Windows7.因此,您可以使用它来获取Network Shell文件夹,然后使用此共享计算机.

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

这里的问题是,这将花费大约10秒,因为网络文件夹被扫描内容.


mat*_*vei 3

I found a Windows-specific solution that allows navigating to any accessible computer node from its name alone (e.g. \\blah or \\blah\), without requiring enumeration of the Network shell folder, or any advance knowledge of network shares on the given node.

Creating a ShellFolder for a computer path

在调试此问题时,我发现ShellFolder必须为给定的计算机路径创建一个才能导航到它。Win32ShellFolderManager2.createShellFolder()将会通知File.getCanonicalPath() on the given file, which will in turn call WinNTFileSystem.canonicalize(). This last call always fails on computer paths. After much experimentation, I was able to create a ShellFolder for any accessible computer path by wrapping the File object in something that bypasses 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Admittedly this solution has a couple edge-cases (e.g. \\blah\ works but \\blah\someShare\..\ does not), and ideally OpenJDK should fix these quirks on their end. This is also an OS-specific and implementation-specific solution, and will not work outside OpenJDK-on-Windows setup.

Integrating with JFileChooser: Option 1

集成它的最简单方法JFileChooser是重写它的approveSelection()方法。这允许用户在对话框中输入计算机路径(\\blah或),然后按 Enter 导航到那里。\\blah\当给出的路径不存在或不可访问时,会显示一条警报消息。

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);
Run Code Online (Sandbox Code Playgroud)

与 JFileChooser 集成:选项 2

或者,FileSystemView可以通过重写其createFileObject(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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

ComputerNodeFriendlyFileSystemView fsv
    = new ComputerNodeFriendlyFileSystemView(FileSystemView.getFileSystemView());
JFileChooser chooser = new JFileChooser("\\\\blah", fsv);
chooser.showOpenDialog(null);
Run Code Online (Sandbox Code Playgroud)