@Override
    public AttributedList<Path> list(final Path directory, final ListProgressListener listener) throws BackgroundException {
        try {
            final AttributedList<Path> objects = new AttributedList<>();
            Marker marker = new Marker(null, null);
            final String containerId = fileid.getVersionId(containerService.getContainer(directory));
            // Seen placeholders
            final Map<String, Long> revisions = new HashMap<>();
            boolean hasDirectoryPlaceholder = containerService.isContainer(directory);
            do {
                if(log.isDebugEnabled()) {
                    log.debug(String.format("List directory %s with marker %s", directory, marker));
                }
                final B2ListFilesResponse response;
                if(versioning.isEnabled()) {
                    // In alphabetical order by file name, and by reverse of date/time uploaded for
                    // versions of files with the same name.
                    response = session.getClient().listFileVersions(containerId,
                            marker.nextFilename, marker.nextFileId, chunksize,
                            this.createPrefix(directory),
                            String.valueOf(Path.DELIMITER));
                }
                else {
                    response = session.getClient().listFileNames(containerId,
                            marker.nextFilename, chunksize,
                            this.createPrefix(directory),
                            String.valueOf(Path.DELIMITER));
                }
                marker = this.parse(directory, objects, response, revisions);
                if(null == marker.nextFileId) {
                    if(!response.getFiles().isEmpty()) {
                        hasDirectoryPlaceholder = true;
                    }
                }
                listener.chunk(directory, objects);
            }
            while(marker.hasNext());
            if(!hasDirectoryPlaceholder && objects.isEmpty()) {
                if(log.isWarnEnabled()) {
                    log.warn(String.format("No placeholder found for directory %s", directory));
                }
                throw new NotfoundException(directory.getAbsolute());
            }
            return objects;
        }
        catch(B2ApiException e) {
            throw new B2ExceptionMappingService(fileid).map("Listing directory {0} failed", e, directory);
        }
        catch(IOException e) {
            throw new DefaultIOExceptionMappingService().map(e);
        }
    }
@Test
    public void testListRevisions() throws Exception {
        final B2VersionIdProvider fileid = new B2VersionIdProvider(session);
        final Path bucket = new B2DirectoryFeature(session, fileid).mkdir(new Path(
                String.format("test-%s", new AsciiRandomStringService().random()), EnumSet.of(Path.Type.directory, Path.Type.volume)), new TransferStatus());
        final String name = new AsciiRandomStringService().random();
        final Path file = new Path(bucket, name, EnumSet.of(Path.Type.file));
        {
            final byte[] content = RandomUtils.nextBytes(1);
            final TransferStatus status = new TransferStatus();
            status.setLength(content.length);
            status.setChecksum(new SHA1ChecksumCompute().compute(new ByteArrayInputStream(content), status));
            final HttpResponseOutputStream<BaseB2Response> out = new B2WriteFeature(session, fileid).write(file, status, new DisabledConnectionCallback());
            IOUtils.write(content, out);
            out.close();
            final B2FileResponse response = (B2FileResponse) out.getStatus();
            assertEquals(response.getFileId(), file.attributes().getVersionId());
            file.attributes().setVersionId(response.getFileId());
            final AttributedList<Path> list = new B2ObjectListService(session, fileid).list(bucket, new DisabledListProgressListener());
            assertTrue(list.contains(file));
            assertNull(list.find(new SimplePathPredicate(file)).attributes().getRevision());
            assertNotNull(list.find(new SimplePathPredicate(file)).attributes().getVersionId());
            assertEquals(content.length, list.find(new SimplePathPredicate(file)).attributes().getSize());
            assertEquals(bucket, list.find(new SimplePathPredicate(file)).getParent());
        }
        // Replace
        {
            final byte[] content = RandomUtils.nextBytes(1);
            final TransferStatus status = new TransferStatus();
            status.setLength(content.length);
            status.setChecksum(new SHA1ChecksumCompute().compute(new ByteArrayInputStream(content), status));
            final HttpResponseOutputStream<BaseB2Response> out = new B2WriteFeature(session, fileid).write(file, status, new DisabledConnectionCallback());
            IOUtils.write(content, out);
            out.close();
            final B2FileResponse response = (B2FileResponse) out.getStatus();
            assertEquals(response.getFileId(), file.attributes().getVersionId());
            final AttributedList<Path> list = new B2ObjectListService(session, fileid).list(bucket, new DisabledListProgressListener());
            assertEquals(2, list.size());
            assertTrue(list.contains(file));
            assertEquals(bucket, list.get(file).getParent());
            assertNull(list.get(file).attributes().getRevision());
            assertNotNull(list.find(new SimplePathPredicate(file)).attributes().getVersionId());
            assertEquals(Long.valueOf(1L), list.find(path -> path.attributes().isDuplicate()).attributes().getRevision());
        }
        // Add hide marker
        new B2DeleteFeature(session, fileid).delete(Collections.singletonList(file.withAttributes(new PathAttributes(file.attributes()).withVersionId(null))), new DisabledLoginCallback(), new Delete.DisabledCallback());
        {
            final AttributedList<Path> list = new B2ObjectListService(session, fileid).list(bucket, new DisabledListProgressListener());
            assertEquals(3, list.size());
            for(Path f : list) {
                assertTrue(f.attributes().isDuplicate());
            }
            assertTrue(new B2ObjectListService(session, fileid, 1, VersioningConfiguration.empty()).list(bucket, new DisabledListProgressListener()).isEmpty());
        }
        assertFalse(new B2FindFeature(session, fileid).find(file));
        assertFalse(new DefaultFindFeature(session).find(file));
        try {
            new B2AttributesFinderFeature(session, fileid).find(file);
            fail();
        }
        catch(NotfoundException e) {
            //
        }
        final AttributedList<Path> list = new B2ObjectListService(session, fileid).list(bucket, new DisabledListProgressListener());
        assertEquals(list, new B2VersioningFeature(session, fileid).list(file, new DisabledListProgressListener()));
        final Path other = new B2TouchFeature(session, fileid).touch(new Path(bucket, name + new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
        final AttributedList<Path> versions = new B2VersioningFeature(session, fileid).list(file, new DisabledListProgressListener());
        assertEquals(list, versions);
        assertFalse(versions.contains(other));
        for(Path f : list) {
            new B2DeleteFeature(session, fileid).delete(Collections.singletonList(f), new DisabledLoginCallback(), new Delete.DisabledCallback());
        }
        new B2DeleteFeature(session, fileid).delete(Arrays.asList(other, bucket), new DisabledLoginCallback(), new Delete.DisabledCallback());
    }