@Override
    public EueWriteFeature.Chunk upload(final Path file, final Local local, final BandwidthThrottle throttle, final StreamListener listener,
                                        final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
        final ThreadPool pool = ThreadPoolFactory.get("multipart", concurrency);
        try {
            final List<Future<EueWriteFeature.Chunk>> parts = new ArrayList<>();
            long offset = 0;
            long remaining = status.getLength();
            final String resourceId;
            final String uploadUri;
            if(status.isExists()) {
                resourceId = fileid.getFileId(file);
                uploadUri = EueUploadHelper.updateResource(session, resourceId, status, UploadType.CHUNKED).getUploadURI();
            }
            else {
                final ResourceCreationResponseEntry uploadResourceCreationResponseEntry = EueUploadHelper.
                        createResource(session, fileid.getFileId(file.getParent()), file.getName(),
                                status, UploadType.CHUNKED);
                resourceId = EueResourceIdProvider.getResourceIdFromResourceUri(uploadResourceCreationResponseEntry.getHeaders().getLocation());
                uploadUri = uploadResourceCreationResponseEntry.getEntity().getUploadURI();
            }
            for(int partNumber = 1; remaining > 0; partNumber++) {
                final long length = Math.min(chunksize, remaining);
                parts.add(this.submit(pool, file, local, throttle, listener, status,
                        uploadUri, resourceId, partNumber, offset, length, callback));
                remaining -= length;
                offset += length;
            }
            // Checksums for uploaded segments
            final List<EueWriteFeature.Chunk> chunks = Interruptibles.awaitAll(parts);
            // Full size of file
            final long size = status.getOffset() + status.getLength();
            final MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            chunks.stream().sorted(Comparator.comparing(EueWriteFeature.Chunk::getPartnumber)).forEach(chunk -> {
                try {
                    messageDigest.update(Hex.decodeHex(chunk.getChecksum().hash));
                }
                catch(DecoderException e) {
                    log.error(String.format("Failure %s decoding hash %s", e, chunk.getChecksum()));
                }
                messageDigest.update(ChunkListSHA256ChecksumCompute.intToBytes(chunk.getLength().intValue()));
            });
            final String cdash64 = Base64.encodeBase64URLSafeString(messageDigest.digest());
            final EueUploadHelper.UploadResponse completedUploadResponse = new EueMultipartUploadCompleter(session)
                    .getCompletedUploadResponse(uploadUri, size, cdash64);
            if(!StringUtils.equals(cdash64, completedUploadResponse.getCdash64())) {
                if(file.getType().contains(Path.Type.encrypted)) {
                    log.warn(String.format("Skip checksum verification for %s with client side encryption enabled", file));
                }
                else {
                    throw new ChecksumException(MessageFormat.format(LocaleFactory.localizedString("Upload {0} failed", "Error"), file.getName()),
                            MessageFormat.format("Mismatch between {0} hash {1} of uploaded data and ETag {2} returned by the server",
                                    HashAlgorithm.cdash64, cdash64, completedUploadResponse.getCdash64()));
                }
            }
            final EueWriteFeature.Chunk object = new EueWriteFeature.Chunk(resourceId, size, cdash64);
            // Mark parent status as complete
            status.withResponse(new EueAttributesAdapter().toAttributes(object)).setComplete();
            return object;
        }
        catch(NoSuchAlgorithmException e) {
            throw new ChecksumException(LocaleFactory.localizedString("Checksum failure", "Error"), e);
        }
        finally {
            // Cancel future tasks
            pool.shutdown(false);
        }
    }
@Test
    public void testUploadLargeFileInChunks() throws Exception {
        final EueResourceIdProvider fileid = new EueResourceIdProvider(session);
        final EueLargeUploadService s = new EueLargeUploadService(session, fileid, new EueWriteFeature(session, fileid));
        final Path container = new EueDirectoryFeature(session, fileid).mkdir(new Path(
                new AlphanumericRandomStringService().random(), EnumSet.of(AbstractPath.Type.directory)), new TransferStatus());
        final String name = new AlphanumericRandomStringService().random();
        final Path file = new Path(container, name, EnumSet.of(Path.Type.file));
        final Local local = new Local(System.getProperty("java.io.tmpdir"), name);
        final byte[] content = RandomUtils.nextBytes(5242881);
        IOUtils.write(content, local.getOutputStream(false));
        final TransferStatus status = new TransferStatus();
        status.setLength(content.length);
        final BytecountStreamListener count = new BytecountStreamListener();
        final EueWriteFeature.Chunk uploadResponse = s.upload(file, local, new BandwidthThrottle(BandwidthThrottle.UNLIMITED), count, status, new DisabledConnectionCallback());
        assertNotNull(uploadResponse.getCdash64());
        assertEquals(content.length, count.getSent());
        assertEquals(PathAttributes.EMPTY, status.getResponse());
        assertTrue(status.isComplete());
        assertTrue(new EueFindFeature(session, fileid).find(file));
        assertEquals(content.length, new EueAttributesFinderFeature(session, fileid).find(file).getSize());
        final byte[] compare = new byte[content.length];
        IOUtils.readFully(new EueReadFeature(session, fileid).read(file, new TransferStatus().withLength(content.length), new DisabledConnectionCallback()), compare);
        assertArrayEquals(content, compare);
        new EueDeleteFeature(session, fileid).delete(Collections.singletonList(container), new DisabledLoginCallback(), new Delete.DisabledCallback());
        local.delete();
    }