net drive support for windows

This commit is contained in:
rdavidek 2026-04-26 20:34:49 +02:00
parent 8d7d039a6a
commit 8a8a7a9416
5 changed files with 437 additions and 58 deletions

View File

@ -15,7 +15,7 @@ import java.io.InputStreamReader;
*/
public class MainApp {
public static final String APP_VERSION = "1.4.2";
public static final String APP_VERSION = "1.4.3";
public enum OS {
WINDOWS, LINUX, MACOS, UNKNOWN

View File

@ -25,32 +25,7 @@ public class DriveSelector extends JDialog {
((JComponent) getContentPane()).setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// Get list of available drives
java.util.Set<File> driveSet = new java.util.LinkedHashSet<>();
File[] roots = File.listRoots();
if (roots != null) {
for (File r : roots) {
driveSet.add(r);
}
}
// On Windows, additionally add mapped network drives if missed by listRoots
if (cz.kamma.kfmanager.MainApp.CURRENT_OS == cz.kamma.kfmanager.MainApp.OS.WINDOWS) {
javax.swing.filechooser.FileSystemView fsv = javax.swing.filechooser.FileSystemView.getFileSystemView();
File[] fsvRoots = fsv.getRoots();
if (fsvRoots != null) {
for (File r : fsvRoots) {
File[] devices = fsv.getFiles(r, false);
if (devices != null) {
for (File d : devices) {
if (fsv.isDrive(d) || fsv.isFileSystemRoot(d)) {
driveSet.add(d);
}
}
}
}
}
}
java.util.List<File> driveSet = DriveUtils.getAvailableDrives();
List<DriveInfo> drives = new ArrayList<>();
for (File drive : driveSet) {

View File

@ -0,0 +1,349 @@
package cz.kamma.kfmanager.ui;
import cz.kamma.kfmanager.MainApp;
import javax.swing.filechooser.FileSystemView;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Helpers for discovering filesystem roots shown by drive selectors.
*/
final class DriveUtils {
private static final Pattern WINDOWS_DRIVE_PATTERN = Pattern.compile("(?i)(?<![A-Z0-9])([A-Z]:)(?![A-Z0-9])");
private static final Pattern WINDOWS_NET_USE_MAPPING_PATTERN =
Pattern.compile("(?i)(?<![A-Z0-9])([A-Z]:)\\s+(\\\\\\\\\\S+)");
private DriveUtils() {
}
static List<File> getAvailableDrives() {
Set<File> drives = new LinkedHashSet<>();
File[] roots = File.listRoots();
if (roots != null) {
for (File root : roots) {
addDrive(drives, root);
}
}
log("File.listRoots(): " + describeFiles(roots));
if (MainApp.CURRENT_OS == MainApp.OS.WINDOWS) {
addWindowsFileSystemViewDrives(drives);
addWindowsMappedNetworkDrives(drives);
}
log("available drives: " + describeFiles(drives));
return new ArrayList<>(drives);
}
static File resolveDriveForLoading(File drive) {
log("resolveDriveForLoading input: " + describeFile(drive));
if (drive == null || MainApp.CURRENT_OS != MainApp.OS.WINDOWS || drive.isDirectory()) {
log("resolveDriveForLoading direct: " + describeFile(drive));
return drive;
}
File normalizedDrive = normalizeDriveKey(drive);
Map<File, File> mappedDrives = getWindowsMappedNetworkDriveTargets();
log("net use mappings: " + describeMappings(mappedDrives));
File mappedTarget = mappedDrives.get(normalizedDrive);
log("resolved mapped target for " + describeFile(normalizedDrive) + ": " + describeFile(mappedTarget));
if (mappedDrives.containsKey(normalizedDrive) && reconnectWindowsMappedDrive(normalizedDrive)) {
log("resolveDriveForLoading reconnected drive accepted: " + describeFile(normalizedDrive));
return normalizedDrive;
}
if (mappedTarget != null && mappedTarget.isDirectory()) {
log("resolveDriveForLoading mapped target accepted: " + describeFile(mappedTarget));
return mappedTarget;
}
log("resolveDriveForLoading fallback to selected drive: " + describeFile(drive));
return drive;
}
static boolean isWindowsMappedOrUncPath(File directory) {
if (directory == null || MainApp.CURRENT_OS != MainApp.OS.WINDOWS) {
return false;
}
String path = directory.getAbsolutePath();
if (path.startsWith("\\\\")) {
return true;
}
try {
java.nio.file.Path rootPath = directory.toPath().toAbsolutePath().normalize().getRoot();
if (rootPath == null) {
return false;
}
File root = rootPath.toFile();
return getWindowsMappedNetworkDriveTargets().containsKey(normalizeDriveKey(root));
} catch (Exception ex) {
log("mapped path check failed for " + describeFile(directory) + ": " + ex.getMessage());
return false;
}
}
static File[] listWindowsDirectoryWithCmd(File directory) {
if (directory == null || MainApp.CURRENT_OS != MainApp.OS.WINDOWS) {
return new File[0];
}
List<String> names = runWindowsDirectoryListCommand(directory);
List<File> files = new ArrayList<>();
for (String name : names) {
if (name == null || name.isBlank() || name.equals(".") || name.equals("..")) {
continue;
}
files.add(new File(directory, name));
}
log("cmd dir entries for " + describeFile(directory) + ": " + describeFiles(files));
return files.toArray(new File[0]);
}
private static void addWindowsFileSystemViewDrives(Set<File> drives) {
try {
FileSystemView fsv = FileSystemView.getFileSystemView();
File[] roots = fsv.getRoots();
if (roots != null) {
for (File root : roots) {
addWindowsShellChildren(drives, fsv, root);
}
}
addWindowsShellChildren(drives, fsv, fsv.getHomeDirectory());
addWindowsShellChildren(drives, fsv, fsv.getDefaultDirectory());
} catch (Exception ignore) {
}
}
private static void addWindowsShellChildren(Set<File> drives, FileSystemView fsv, File root) {
if (root == null) {
return;
}
try {
if (fsv.isDrive(root) || fsv.isFileSystemRoot(root)) {
addDrive(drives, root);
}
File[] files = fsv.getFiles(root, false);
if (files == null) {
return;
}
for (File file : files) {
if (fsv.isDrive(file) || fsv.isFileSystemRoot(file)) {
addDrive(drives, file);
}
}
} catch (Exception ignore) {
}
}
private static void addWindowsMappedNetworkDrives(Set<File> drives) {
for (File drive : getWindowsMappedNetworkDriveTargets().keySet()) {
addDrive(drives, drive);
}
}
private static Map<File, File> getWindowsMappedNetworkDriveTargets() {
Map<File, File> mappedDrives = new LinkedHashMap<>();
Process process = null;
try {
process = new ProcessBuilder("cmd.exe", "/c", "net use")
.redirectErrorStream(true)
.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
String line;
while ((line = reader.readLine()) != null) {
log("net use line: " + line);
Matcher mappingMatcher = WINDOWS_NET_USE_MAPPING_PATTERN.matcher(line);
if (mappingMatcher.find()) {
mappedDrives.put(
normalizeDriveKey(new File(mappingMatcher.group(1) + "\\")),
new File(mappingMatcher.group(2)));
continue;
}
Matcher driveMatcher = WINDOWS_DRIVE_PATTERN.matcher(line);
while (driveMatcher.find()) {
mappedDrives.putIfAbsent(
normalizeDriveKey(new File(driveMatcher.group(1) + "\\")),
null);
}
}
}
if (process.waitFor(2, TimeUnit.SECONDS)) {
log("net use exit code: " + process.exitValue());
} else {
log("net use did not finish within timeout");
}
} catch (Exception ignore) {
log("net use failed: " + ignore.getMessage());
} finally {
if (process != null && process.isAlive()) {
process.destroyForcibly();
}
}
return mappedDrives;
}
private static boolean reconnectWindowsMappedDrive(File drive) {
File normalizedDrive = normalizeDriveKey(drive);
if (normalizedDrive == null) {
return false;
}
log("attempting mapped drive reconnect via dir: " + describeFile(normalizedDrive));
int exitCode = runWindowsProbeCommand("cmd.exe", "/c", "dir", normalizedDrive.getPath());
log("mapped drive reconnect dir exit code: " + exitCode + ", after: " + describeFile(normalizedDrive));
return normalizedDrive.isDirectory();
}
private static int runWindowsProbeCommand(String... command) {
Process process = null;
try {
process = new ProcessBuilder(command)
.redirectErrorStream(true)
.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
String line;
while ((line = reader.readLine()) != null) {
log("probe line: " + line);
}
}
if (process.waitFor(5, TimeUnit.SECONDS)) {
return process.exitValue();
}
log("probe did not finish within timeout");
} catch (Exception ex) {
log("probe failed: " + ex.getMessage());
} finally {
if (process != null && process.isAlive()) {
process.destroyForcibly();
}
}
return -1;
}
private static List<String> runWindowsDirectoryListCommand(File directory) {
List<String> lines = new ArrayList<>();
Process process = null;
try {
process = new ProcessBuilder("cmd.exe", "/c", "dir", "/a", "/b", directory.getPath())
.redirectErrorStream(true)
.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
String line;
while ((line = reader.readLine()) != null) {
log("dir list line: " + line);
lines.add(line);
}
}
if (process.waitFor(5, TimeUnit.SECONDS)) {
int exitCode = process.exitValue();
log("dir list exit code: " + exitCode);
if (exitCode != 0) {
return new ArrayList<>();
}
} else {
log("dir list did not finish within timeout");
return new ArrayList<>();
}
} catch (Exception ex) {
log("dir list failed: " + ex.getMessage());
return new ArrayList<>();
} finally {
if (process != null && process.isAlive()) {
process.destroyForcibly();
}
}
return lines;
}
private static File normalizeDriveKey(File drive) {
if (drive == null) {
return null;
}
Matcher matcher = WINDOWS_DRIVE_PATTERN.matcher(drive.getAbsolutePath());
if (matcher.find() && matcher.start() == 0) {
return new File(matcher.group(1).toUpperCase(Locale.ROOT) + "\\");
}
return drive.getAbsoluteFile();
}
private static void addDrive(Set<File> drives, File drive) {
if (drive == null) {
return;
}
drives.add(normalizeDriveKey(drive));
}
private static void log(String message) {
System.err.println("[DRIVE] " + message);
}
private static String describeFile(File file) {
if (file == null) {
return "<null>";
}
try {
return file.getPath()
+ " [abs=" + file.getAbsolutePath()
+ ", exists=" + file.exists()
+ ", dir=" + file.isDirectory()
+ ", canRead=" + file.canRead()
+ "]";
} catch (Exception ex) {
return file.getPath() + " [describe failed: " + ex.getMessage() + "]";
}
}
private static String describeFiles(File[] files) {
if (files == null) {
return "<null>";
}
return describeFiles(Arrays.asList(files));
}
private static String describeFiles(Iterable<File> files) {
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (File file : files) {
if (!first) {
sb.append(", ");
}
sb.append(describeFile(file));
first = false;
}
return sb.append(']').toString();
}
private static String describeMappings(Map<File, File> mappings) {
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (Map.Entry<File, File> entry : mappings.entrySet()) {
if (!first) {
sb.append(", ");
}
sb.append(describeFile(entry.getKey())).append(" -> ").append(describeFile(entry.getValue()));
first = false;
}
return sb.append(']').toString();
}
}

View File

@ -125,20 +125,28 @@ public class FilePanel extends JPanel {
Object selObj = driveCombo.getSelectedItem();
if (selObj instanceof File sel) {
System.err.println("[DRIVE] combo selected: " + describeDriveForLog(sel));
FilePanelTab currentTab = getCurrentTab();
if (currentTab != null) {
File targetDirectory = sel;
File targetDirectory = DriveUtils.resolveDriveForLoading(sel);
System.err.println("[DRIVE] target after drive resolver: " + describeDriveForLog(targetDirectory));
if (driveSelectionTargetResolver != null) {
try {
File resolved = driveSelectionTargetResolver.apply(sel);
if (resolved != null && resolved.exists() && resolved.isDirectory() && isWithinSelectedDrive(resolved, sel)) {
File resolved = driveSelectionTargetResolver.apply(targetDirectory);
System.err.println("[DRIVE] opposite-panel resolver returned: " + describeDriveForLog(resolved));
if (resolved != null && resolved.exists() && resolved.isDirectory() && isWithinSelectedDrive(resolved, targetDirectory)) {
targetDirectory = resolved;
System.err.println("[DRIVE] opposite-panel resolver accepted: " + describeDriveForLog(targetDirectory));
} else {
System.err.println("[DRIVE] opposite-panel resolver rejected for selected drive: " + describeDriveForLog(targetDirectory));
}
} catch (Exception ignore) {
} catch (Exception ex) {
System.err.println("[DRIVE] opposite-panel resolver failed: " + ex.getMessage());
// fall back to selected drive root
}
}
System.err.println("[DRIVE] loading selected drive target: " + describeDriveForLog(targetDirectory));
currentTab.loadDirectory(targetDirectory);
SwingUtilities.invokeLater(() -> {
try { currentTab.getFileTable().requestFocusInWindow(); } catch (Exception ignore) {}
@ -269,6 +277,22 @@ public class FilePanel extends JPanel {
}
}
private static String describeDriveForLog(File file) {
if (file == null) {
return "<null>";
}
try {
return file.getPath()
+ " [abs=" + file.getAbsolutePath()
+ ", exists=" + file.exists()
+ ", dir=" + file.isDirectory()
+ ", canRead=" + file.canRead()
+ "]";
} catch (Exception ex) {
return file.getPath() + " [describe failed: " + ex.getMessage() + "]";
}
}
private boolean isWithinSelectedDrive(File directory, File selectedDrive) {
try {
java.nio.file.Path dirPath = directory.toPath().toAbsolutePath().normalize();
@ -649,33 +673,7 @@ public class FilePanel extends JPanel {
try {
driveCombo.removeAllItems();
java.util.Set<File> driveSet = new java.util.LinkedHashSet<>();
// Add standard roots
File[] roots = File.listRoots();
if (roots != null) {
for (File r : roots) {
driveSet.add(r);
}
}
// On Windows, additionally add mapped network drives if missed by listRoots
if (MainApp.CURRENT_OS == MainApp.OS.WINDOWS) {
javax.swing.filechooser.FileSystemView fsv = javax.swing.filechooser.FileSystemView.getFileSystemView();
File[] fsvRoots = fsv.getRoots();
if (fsvRoots != null) {
for (File r : fsvRoots) {
// This usually returns "Desktop". We need to look into it or other FSV methods.
File[] devices = fsv.getFiles(r, false);
if (devices != null) {
for (File d : devices) {
if (fsv.isDrive(d) || fsv.isFileSystemRoot(d)) {
driveSet.add(d);
}
}
}
}
}
}
driveSet.addAll(DriveUtils.getAvailableDrives());
// Add home directory
driveSet.add(new File(System.getProperty("user.home")));

View File

@ -1473,24 +1473,34 @@ public class FilePanelTab extends JPanel {
}
public void loadDirectory(File directory, List<FileItem> preloadedItems, boolean autoSelectFirst, boolean requestFocus, final Runnable postLoadAction) {
File requestedDirectory = directory;
System.err.println("[DRIVE] FilePanelTab.loadDirectory requested: " + describeDirectoryForLog(requestedDirectory));
// Ensure we load an existing directory - try parents if necessary
File dirToLoad = directory;
while (dirToLoad != null && !dirToLoad.isDirectory()) {
System.err.println("[DRIVE] loadDirectory candidate is not directory, trying parent: " + describeDirectoryForLog(dirToLoad));
dirToLoad = dirToLoad.getParentFile();
}
if (dirToLoad == null) {
dirToLoad = new File(System.getProperty("user.home"));
System.err.println("[DRIVE] loadDirectory fell back to user.home: " + describeDirectoryForLog(dirToLoad));
if (!dirToLoad.isDirectory()) {
dirToLoad = new File(File.separator);
System.err.println("[DRIVE] loadDirectory fell back to File.separator: " + describeDirectoryForLog(dirToLoad));
}
}
directory = dirToLoad;
if (requestedDirectory != directory) {
System.err.println("[DRIVE] loadDirectory final candidate: " + describeDirectoryForLog(directory));
}
// If we are switching directories, cleanup any previously extracted archive temp dirs
cleanupArchiveTempDirIfNeeded(directory);
if (directory == null || !directory.isDirectory()) {
System.err.println("[DRIVE] loadDirectory aborted, final candidate is invalid: " + describeDirectoryForLog(directory));
return;
}
@ -1847,6 +1857,15 @@ public class FilePanelTab extends JPanel {
return new ArrayList<>();
}
File[] files = directory.listFiles();
if (MainApp.CURRENT_OS == MainApp.OS.WINDOWS && DriveUtils.isWindowsMappedOrUncPath(directory)) {
File[] cmdFiles = DriveUtils.listWindowsDirectoryWithCmd(directory);
System.err.println("[DRIVE] listFiles count=" + (files != null ? files.length : -1)
+ ", cmd dir count=" + cmdFiles.length
+ " for " + describeDirectoryForLog(directory));
files = mergeFileArrays(files, cmdFiles);
System.err.println("[DRIVE] merged directory entry count=" + (files != null ? files.length : -1)
+ " for " + describeDirectoryForLog(directory));
}
List<FileItem> items = new ArrayList<>();
File parent = directory.getParentFile();
if (parent != null) {
@ -1868,6 +1887,28 @@ public class FilePanelTab extends JPanel {
return items;
}
private File[] mergeFileArrays(File[] primary, File[] secondary) {
if (primary == null || primary.length == 0) {
return secondary != null ? secondary : new File[0];
}
if (secondary == null || secondary.length == 0) {
return primary;
}
Map<String, File> merged = new LinkedHashMap<>();
for (File file : primary) {
if (file != null) {
merged.put(file.getAbsolutePath().toLowerCase(Locale.ROOT), file);
}
}
for (File file : secondary) {
if (file != null) {
merged.putIfAbsent(file.getAbsolutePath().toLowerCase(Locale.ROOT), file);
}
}
return merged.values().toArray(new File[0]);
}
private boolean isSameContent(List<FileItem> list1, List<FileItem> list2) {
if (list1.size() != list2.size()) return false;
for (int i = 0; i < list1.size(); i++) {
@ -4421,6 +4462,22 @@ public class FilePanelTab extends JPanel {
return currentDirectory;
}
private static String describeDirectoryForLog(File file) {
if (file == null) {
return "<null>";
}
try {
return file.getPath()
+ " [abs=" + file.getAbsolutePath()
+ ", exists=" + file.exists()
+ ", dir=" + file.isDirectory()
+ ", canRead=" + file.canRead()
+ "]";
} catch (Exception ex) {
return file.getPath() + " [describe failed: " + ex.getMessage() + "]";
}
}
// FileTableModel
private class FileTableModel extends AbstractTableModel {
private List<FileItem> items = new ArrayList<>();