@Override
    public Path copy(final Path source, final Path target, final TransferStatus status, final ConnectionCallback callback, final StreamListener listener) throws BackgroundException {
        try {
            final Storage.Objects.Get request = session.getClient().objects().get(containerService.getContainer(source).getName(), containerService.getKey(source));
            if(containerService.getContainer(containerService.getContainer(source)).attributes().getCustom().containsKey(GoogleStorageAttributesFinderFeature.KEY_REQUESTER_PAYS)) {
                request.setUserProject(session.getHost().getCredentials().getUsername());
            }
            if(StringUtils.isNotBlank(source.attributes().getVersionId())) {
                request.setGeneration(Long.parseLong(source.attributes().getVersionId()));
            }
            final StorageObject storageObject = request.execute();
            if(null != status.getModified()) {
                storageObject.setCustomTime(new DateTime(status.getModified()));
            }
            final Storage.Objects.Rewrite rewrite = session.getClient().objects().rewrite(containerService.getContainer(source).getName(), containerService.getKey(source),
                    containerService.getContainer(target).getName(), containerService.getKey(target), storageObject);
            if(containerService.getContainer(source).attributes().getCustom().containsKey(GoogleStorageAttributesFinderFeature.KEY_REQUESTER_PAYS)) {
                rewrite.setUserProject(session.getHost().getCredentials().getUsername());
            }
            RewriteResponse response;
            do {
                response = rewrite.execute();
                // Include this field (from the previous rewrite response) on each rewrite request after the first one,
                // until the rewrite response 'done' flag is true.
                rewrite.setRewriteToken(response.getRewriteToken());
            }
            while(!response.getDone());
            listener.sent(status.getLength());
            return target.withAttributes(new GoogleStorageAttributesFinderFeature(session).toAttributes(response.getResource()));
        }
        catch(IOException e) {
            throw new GoogleStorageExceptionMappingService().map("Cannot copy {0}", e, source);
        }
    }
@Test
    public void testCopyFileZeroLength() throws Exception {
        final Path container = new Path("cyberduck-test-eu", EnumSet.of(Path.Type.directory, Path.Type.volume));
        final Path test = new Path(container, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.file));
        test.attributes().setSize(0L);
        new GoogleStorageTouchFeature(session).touch(test, new TransferStatus().withMime("application/cyberduck").withMetadata(Collections.singletonMap("cyberduck", "set")));
        final Path copy = new Path(container, new AsciiRandomStringService().random(), EnumSet.of(Path.Type.file));
        final GoogleStorageCopyFeature feature = new GoogleStorageCopyFeature(session);
        assertTrue(feature.isSupported(test, copy));
        feature.copy(test, copy, new TransferStatus(), new DisabledConnectionCallback(), new DisabledStreamListener());
        assertTrue(new GoogleStorageFindFeature(session).find(test));
        new GoogleStorageDeleteFeature(session).delete(Collections.singletonList(test), new DisabledLoginCallback(), new Delete.DisabledCallback());
        assertTrue(new GoogleStorageFindFeature(session).find(copy));
        assertEquals("set",
            new GoogleStorageMetadataFeature(session).getMetadata(copy).get("cyberduck"));
        new GoogleStorageDeleteFeature(session).delete(Collections.singletonList(copy), new DisabledLoginCallback(), new Delete.DisabledCallback());
    }