Skip to content

Commit cbd1ffe

Browse files
committed
SNT Stream: Improve detection of invalid paths to local files
1 parent 904bf26 commit cbd1ffe

3 files changed

Lines changed: 59 additions & 2 deletions

File tree

‎src/main/java/sc/fiji/snt/gui/cmds/BigDataLoaderCmd.java‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ public void run() {
237237
return;
238238
}
239239
if (!checkReachable(filePaths)) return; // single friendly dialog if any remote field is unreachable
240+
if (!checkContainerMetadata(filePaths)) return; // before the splash screen is shown
240241
final boolean threeD = viewerType != null && viewerType.toLowerCase().contains("bvv");
241242
final boolean tracer = tracingEnabled;
242243

@@ -334,8 +335,28 @@ private boolean checkReachable(final String[] filePaths) {
334335
return false;
335336
}
336337

337-
/** True if path is an existing .n5/.zarr directory, or a remote URL to one. */
338-
private static boolean isN5OrZarrDir(final String path) {
338+
/**
339+
* Reports (and returns false for) any local N5/Zarr volume without root metadata, e.g., an empty directory
340+
* (hidden files such as .DS_Store do not count). Opening such a directory fails in n5-ij, and falling back to
341+
* its interactive dataset dialog does not recover
342+
*/
343+
private boolean checkContainerMetadata(final String[] filePaths) {
344+
for (int i = 0; i < Math.min(2, filePaths.length); i++) {
345+
final String path = filePaths[i];
346+
if (SpimDataUtils.isRemoteUrl(path) || !looksLikeN5OrZarrDir(path)) continue;
347+
SNTUtils.log("BVV: checking container metadata of " + path);
348+
final File dir = new File(path);
349+
if (!ImgUtils.hasN5ZarrMetadata(dir)) {
350+
error("<HTML>'" + GuiUtils.Text.escapeHtml(dir.getName()) + "' does not seem to be a valid "
351+
+ "N5/Zarr container: no metadata found.<br>Is the directory empty or incomplete?");
352+
return false;
353+
}
354+
}
355+
return true;
356+
}
357+
358+
/** True if path is a .n5/.zarr directory (that exists), or a remote URL to one. Does not check its content */
359+
private static boolean looksLikeN5OrZarrDir(final String path) {
339360
if (SpimDataUtils.isRemoteUrl(path)) {
340361
final String lower = path.toLowerCase();
341362
return lower.endsWith(".n5") || lower.endsWith(".n5/") || lower.endsWith(".zarr") || lower.endsWith(".zarr/");
@@ -345,6 +366,16 @@ private static boolean isN5OrZarrDir(final String path) {
345366
return f.isDirectory() && (lower.endsWith(".n5") || lower.endsWith(".zarr"));
346367
}
347368

369+
/**
370+
* True if path is a container that the interactive dataset dialog can recover (see {@link #datasetDialog}): a
371+
* remote N5/Zarr URL, or a local N5/Zarr directory with metadata. A local directory without metadata
372+
* (e.g., an empty one) is not: n5-ij cannot open it and its dialog keeps retrying
373+
*/
374+
private static boolean isN5OrZarrDir(final String path) {
375+
if (!looksLikeN5OrZarrDir(path)) return false;
376+
return SpimDataUtils.isRemoteUrl(path) || ImgUtils.hasN5ZarrMetadata(new File(path));
377+
}
378+
348379
/**
349380
* Fallback UI for when {@link SpimDataUtils#resolvePathToSource(String)} cannot auto-discover a dataset in an
350381
* N5/Zarr container on its own (e.g. an ambiguous or unusually structured container). Lets the user pick a

‎src/main/java/sc/fiji/snt/io/SpimDataUtils.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ private static Region probeS3Region(final String bucket) {
509509
* recognized metadata is found
510510
*/
511511
private static N5Sources resolveN5ToSources(final File dir) {
512+
if (!sc.fiji.snt.util.ImgUtils.hasN5ZarrMetadata(dir))
513+
throw new IllegalArgumentException("'" + dir.getName() + "' is not a valid N5/Zarr container (no metadata found)");
512514
return resolveN5ToSources(dir.getAbsolutePath(), dir.getName());
513515
}
514516

@@ -525,13 +527,20 @@ private static N5Sources resolveN5ToSources(final File dir) {
525527
private static N5Sources resolveN5ToSources(final String rootPath, final String name) {
526528
if (rootPath.toLowerCase(Locale.ROOT).startsWith("s3://"))
527529
return resolveS3ToSources(rootPath, name);
530+
// N5ViewerReaderFun reports failures through IJ.error() (a modal dialog) instead of throwing, and returns
531+
// null. Redirect them to the log: the caller handles the exception below
532+
final boolean redirecting = ij.IJ.redirectingErrorMessages();
528533
try {
534+
ij.IJ.redirectErrorMessages(true);
529535
final N5Reader n5 = new N5Importer.N5ViewerReaderFun().apply(rootPath);
536+
ij.IJ.redirectErrorMessages(redirecting);
530537
if (n5 == null)
531538
throw new IllegalArgumentException("Could not open N5/Zarr container: " + rootPath);
532539
return discoverAndBuild(n5, rootPath, name);
533540
} catch (final IOException | RuntimeException e) {
534541
throw new IllegalArgumentException("Could not open N5/Zarr container: " + e.getMessage(), e);
542+
} finally {
543+
ij.IJ.redirectErrorMessages(redirecting);
535544
}
536545
}
537546

‎src/main/java/sc/fiji/snt/util/ImgUtils.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,23 @@ private static void writeOmeNgffMetadata(final org.janelia.saalfeldlab.n5.N5Writ
20482048
}
20492049
}
20502050

2051+
/** Root metadata files of N5 and Zarr (v2/v3) containers */
2052+
private static final List<String> CONTAINER_METADATA = List.of("zarr.json", ".zgroup", ".zarray", ".zattrs",
2053+
"attributes.json");
2054+
2055+
/**
2056+
* Checks whether a local directory has the root metadata of an N5 or Zarr (v2/v3) container, i.e., whether
2057+
* it can be opened as one. An empty or incomplete directory (hidden files such as {@code .DS_Store} do not
2058+
* count) fails this check. Does not validate the metadata's content
2059+
*
2060+
* @param dir the directory to check
2061+
* @return true if {@code dir} is a directory containing {@code zarr.json}, {@code .zgroup}, {@code .zarray},
2062+
* {@code .zattrs} or {@code attributes.json}
2063+
*/
2064+
public static boolean hasN5ZarrMetadata(final File dir) {
2065+
return dir != null && dir.isDirectory() && CONTAINER_METADATA.stream().anyMatch(n -> new File(dir, n).isFile());
2066+
}
2067+
20512068
/**
20522069
* Resolves a path to a local OME-Zarr (OME-NGFF v0.5) container directory. Accepts the directory itself or its
20532070
* {@code zarr.json} file (as returned by some file choosers)

0 commit comments

Comments
 (0)