@Override
    public HttpResponseOutputStream<File> write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
        final DelayedHttpEntityCallable<File> command = new DelayedHttpEntityCallable<File>(file) {
            @Override
            public File call(final HttpEntity entity) throws BackgroundException {
                // Initiate a resumable upload
                String location;
                try {
                    location = start(file, status);
                }
                catch(InteroperabilityException e) {
                    if(null == status.getLockId()) {
                        throw e;
                    }
                    location = start(file, status.withLockId(null));
                }
                final StoregateApiClient client = session.getClient();
                try {
                    // Upload the file
                    final HttpPut put = new HttpPut(location);
                    put.setEntity(entity);
                    final String header;
                    if(status.getLength() == 0) {
                        // Touch
                        header = "*/0";
                    }
                    else {
                        final HttpRange range = HttpRange.byLength(0, status.getLength());
                        header = String.format("%d-%d/%d", range.getStart(), range.getEnd(), status.getLength());
                    }
                    put.addHeader(HttpHeaders.CONTENT_RANGE, String.format("bytes %s", header));
                    final HttpResponse putResponse = client.getClient().execute(put);
                    try {
                        switch(putResponse.getStatusLine().getStatusCode()) {
                            case HttpStatus.SC_OK:
                            case HttpStatus.SC_CREATED:
                                final File result = new JSON().getContext(FileMetadata.class).readValue(
                                        new InputStreamReader(putResponse.getEntity().getContent(), StandardCharsets.UTF_8),
                                        File.class);
                                fileid.cache(file, result.getId());
                                return result;
                            default:
                                throw new StoregateExceptionMappingService(fileid).map("Upload {0} failed",
                                        new ApiException(putResponse.getStatusLine().getStatusCode(), putResponse.getStatusLine().getReasonPhrase(), Collections.emptyMap(),
                                                EntityUtils.toString(putResponse.getEntity())), file);
                        }
                    }
                    catch(BackgroundException e) {
                        // Cancel upload on error reply
                        cancel(file, location);
                        throw e;
                    }
                    finally {
                        EntityUtils.consume(putResponse.getEntity());
                    }
                }
                catch(IOException e) {
                    // Cancel upload on I/O failure
                    cancel(file, location);
                    throw new HttpExceptionMappingService().map("Upload {0} failed", e, file);
                }
            }

            @Override
            public long getContentLength() {
                return status.getLength();
            }
        };
        return this.write(file, status, command);
    }
@Test
    public void testReadWrite() throws Exception {
        final StoregateIdProvider nodeid = new StoregateIdProvider(session);
        final Path room = new StoregateDirectoryFeature(session, nodeid).mkdir(
                new Path(String.format("/My files/%s", new AlphanumericRandomStringService().random()),
                        EnumSet.of(Path.Type.directory, Path.Type.volume)), new TransferStatus());
        final long folderTimestamp = new StoregateAttributesFinderFeature(session, nodeid).find(room).getModificationDate();
        final byte[] content = RandomUtils.nextBytes(32769);
        final Path test = new Path(room, String.format("%s", new AlphanumericRandomStringService().random()), EnumSet.of(Path.Type.file));
        final File version;
        {
            final TransferStatus status = new TransferStatus();
            status.setLength(content.length);
            status.setModified(1620113107725L);
            status.setCreated(1695160857860L);
            final StoregateWriteFeature writer = new StoregateWriteFeature(session, nodeid);
            final HttpResponseOutputStream<File> out = writer.write(test, status, new DisabledConnectionCallback());
            assertNotNull(out);
            new StreamCopier(status, status).transfer(new ByteArrayInputStream(content), out);
            version = out.getStatus();
        }
        assertNotNull(version);
        assertTrue(new DefaultFindFeature(session).find(test));
        assertEquals(folderTimestamp, new StoregateAttributesFinderFeature(session, nodeid).find(room).getModificationDate());
        PathAttributes attributes = new StoregateAttributesFinderFeature(session, nodeid).find(test);
        assertEquals(1695160857860L, attributes.getCreationDate());
        assertEquals(1620113107725L, attributes.getModificationDate());
        final String versionId = attributes.getVersionId();
        assertNull(versionId);
        final String nodeId = attributes.getFileId();
        assertNotNull(nodeId);
        assertEquals(new StoregateAttributesFinderFeature(session, nodeid).toAttributes(version), attributes);
        final byte[] compare = new byte[content.length];
        final InputStream stream = new StoregateReadFeature(session, nodeid).read(test, new TransferStatus().withLength(content.length), new DisabledConnectionCallback());
        IOUtils.readFully(stream, compare);
        stream.close();
        assertArrayEquals(content, compare);
        // Overwrite
        {
            final byte[] change = RandomUtils.nextBytes(256);
            final TransferStatus status = new TransferStatus();
            status.setLength(change.length);
            final StoregateWriteFeature writer = new StoregateWriteFeature(session, nodeid);
            final HttpResponseOutputStream<File> out = writer.write(test, status.exists(true), new DisabledConnectionCallback());
            assertNotNull(out);
            new StreamCopier(status, status).transfer(new ByteArrayInputStream(change), out);
            assertEquals(nodeId, out.getStatus().getId());
        }
        test.attributes().setCustom(Collections.emptyMap());
        attributes = new StoregateAttributesFinderFeature(session, nodeid).find(test);
        assertNotNull(attributes.getFileId());
        assertEquals(nodeId, new StoregateIdProvider(session).getFileId(test));
        new StoregateDeleteFeature(session, nodeid).delete(Collections.singletonList(room), new DisabledLoginCallback(), new Delete.DisabledCallback());
    }