@Override
    public String getVersionId(final Path file) throws BackgroundException {
        if(StringUtils.isNotBlank(file.attributes().getVersionId())) {
            if(log.isDebugEnabled()) {
                log.debug(String.format("Return version %s from attributes for file %s", file.attributes().getVersionId(), file));
            }
            return file.attributes().getVersionId();
        }
        final String cached = super.getVersionId(file);
        if(cached != null) {
            if(log.isDebugEnabled()) {
                log.debug(String.format("Return cached versionid %s for file %s", cached, file));
            }
            return cached;
        }
        try {
            if(containerService.isContainer(file)) {
                final B2BucketResponse info = session.getClient().listBucket(file.getName());
                if(null == info) {
                    throw new NotfoundException(file.getAbsolute());
                }
                // Cache in file attributes
                return this.cache(file, info.getBucketId());
            }
            // Files that have been hidden will not be returned
            final B2ListFilesResponse response = session.getClient().listFileNames(
                    this.getVersionId(containerService.getContainer(file)), containerService.getKey(file), 1,
                    new DirectoryDelimiterPathContainerService().getKey(file.getParent()),
                    null);
            // Find for exact filename match (.bzEmpty file for directories)
            final Optional<B2FileInfoResponse> optional = response.getFiles().stream().filter(
                    info -> StringUtils.equals(containerService.getKey(file), info.getFileName())).findFirst();
            if(optional.isPresent()) {
                // Cache in file attributes
                return this.cache(file, optional.get().getFileId());
            }
            if(file.isDirectory()) {
                // Search for common prefix returned when no placeholder file was found
                if(response.getFiles().stream().anyMatch(
                        info -> StringUtils.startsWith(info.getFileName(), new DirectoryDelimiterPathContainerService().getKey(file)))) {
                    if(log.isDebugEnabled()) {
                        log.debug(String.format("Common prefix found for %s but no placeholder file", file));
                    }
                    return null;
                }
                throw new NotfoundException(file.getAbsolute());
            }
            throw new NotfoundException(file.getAbsolute());
        }
        catch(B2ApiException e) {
            throw new B2ExceptionMappingService(this).map(e);
        }
        catch(IOException e) {
            throw new DefaultIOExceptionMappingService().map(e);
        }
    }
@Test
    public void getFileIdFile() throws Exception {
        final B2VersionIdProvider fileid = new B2VersionIdProvider(session);
        final Path bucket = new B2DirectoryFeature(session, fileid).mkdir(new Path(new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.directory, Path.Type.volume)), new TransferStatus());
        final Path file = new B2TouchFeature(session, fileid).touch(new Path(bucket, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
        final String versionId = fileid.getVersionId(file);
        assertNotNull(versionId);
        assertEquals(file.attributes().getVersionId(), versionId);
        try {
            assertNull(fileid.getVersionId(new Path(bucket, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file))));
            fail();
        }
        catch(NotfoundException e) {
            // Expected
        }
        final PathAttributes duplicate = new PathAttributes();
        duplicate.setVersionId("d");
        duplicate.setDuplicate(true);
        fileid.cache(new Path(file).withAttributes(duplicate), "d");
        assertEquals(versionId, fileid.getVersionId(file));
        new B2DeleteFeature(session, fileid).delete(Arrays.asList(bucket, file), new DisabledLoginCallback(), new Delete.DisabledCallback());
    }