class PrunableArchiveMigrator {

    public void migrate() {
        UUID chainId = blockchainConfig.getChain().getChainId();
        String migrateOption = optionDAO.get(MIGRATE_OPTION);
        if (migrateOption == null) {
            String currentShard = optionDAO.get(CURRENT_SHARD_OPTION);
            long shardIdForMigration = 1;
            if (currentShard != null) {
                shardIdForMigration = Long.parseLong(currentShard);
            }
            long finalShardIdForMigration = shardIdForMigration;
            List<Shard> shardsForMigration = shardDao.getAllCompletedShards().stream().filter(shard -> shard.getShardId() >= finalShardIdForMigration).sorted(Comparator.comparing(Shard::getShardId)).collect(Collectors.toList());
            List<String> tablesToExclude = registry.getDerivedTables().stream().filter(t -> t instanceof PrunableDbTable).map(DerivedTableInterface::getName).collect(Collectors.toList());
            tablesToExclude.add(ShardConstants.DATA_TAG_TABLE_NAME);
            for (Shard shard : shardsForMigration) {
                try {
                    optionDAO.set(CURRENT_SHARD_OPTION, String.valueOf(shard.getShardId()));
                    Path tempDirectory = Files.createTempDirectory("prunable-shard-migration-" + shard.getShardId());
                    ShardNameHelper shardNameHelper = new ShardNameHelper();
                    Path shardArchivePath = dirProvider.getDataExportDir().resolve(shardNameHelper.getCoreShardArchiveNameByShardId(shard.getShardId(), chainId));

                    String tempDirectoryString = tempDirectory.toAbsolutePath().toString();
                    zip.extract(shardArchivePath.toAbsolutePath().toString(), tempDirectoryString, true);
                    CsvExporter csvExporter = createExporter(tempDirectory);
                    csvExporter.exportShardTableIgnoringLastZipHashes(shard.getShardHeight(), 100);
                    String zipName = "shard-" + shard.getShardId() + ".zip";
                    Path newArchive = tempDirectory.resolve(zipName);
                    ChunkedFileOps fops = zip.compressAndHash(newArchive.toAbsolutePath().toString(), tempDirectoryString, 0L, (dir, name) -> {
                        int csvSuffixPos = name.indexOf(".csv");
                        return csvSuffixPos != -1 && !tablesToExclude.contains(name.substring(0, csvSuffixPos));
                    }, false);
                    if (fops != null && fops.isHashedOK()) {
                        byte[] hash = fops.getFileHash();
                        shard.setCoreZipHash(hash);
                        shard.setPrunableZipHash(new byte[32]); // not null to force prunable archive recreation
                        shardDao.updateShard(shard);
                        //inform DownloadableFileManafer about file change
                        fops.moveFile(shardArchivePath);
                        fops.setFileId(shardNameHelper.getFullShardPrunId(shard.getShardId(), chainId));
                        fileChangedEvent.select(new AnnotationLiteral<FileChangedEvent>() {
                        }).fireAsync(fops);
                        log.debug("Firing 'FILE_CHANDED' event {}", fops.getFileId());
                    } else {
                        log.error("Can not comperess prunable zip: {}", zipName);
                    }
                    FileUtils.clearDirectorySilently(tempDirectory); // clean is not mandatory, but desirable
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            optionDAO.set(MIGRATE_OPTION, "false");
        }
    }

    @Inject public  PrunableArchiveMigrator(Event<ChunkedFileOps> fileChangedEvent, ShardDao shardDao, OptionDAO optionDAO, DirProvider dirProvider, BlockchainConfig blockchainConfig, Zip zip, DerivedTablesRegistry registry, DatabaseManager databaseManager, CsvEscaper translator);

}