Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 49ba558a authored by Josh Gao's avatar Josh Gao
Browse files

adb: add interfaces for Encoder/Decoder.

More groundwork to support more compression algorithms.

Bug: https://issuetracker.google.com/150827486
Test: python3 -m unittest test_device.FileOperationsTest{Uncompressed,Brotli}
Change-Id: I638493083b83e3f6c6854b631471e9d6b50bd79f
parent 73d44bb6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -290,7 +290,7 @@ static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy)
        }
    }

    if (do_sync_push(apk_file, apk_dest.c_str(), false, true)) {
    if (do_sync_push(apk_file, apk_dest.c_str(), false, CompressionType::Any)) {
        result = pm_command(argc, argv);
        delete_device_file(apk_dest);
    }
+1 −1
Original line number Diff line number Diff line
@@ -282,5 +282,5 @@ int Bugreport::SendShellCommand(const std::string& command, bool disable_shell_p

bool Bugreport::DoSyncPull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
                           const char* name) {
    return do_sync_pull(srcs, dst, copy_attrs, false, name);
    return do_sync_pull(srcs, dst, copy_attrs, CompressionType::None, name);
}
+52 −31
Original line number Diff line number Diff line
@@ -129,20 +129,20 @@ static void help() {
        " reverse --remove-all     remove all reverse socket connections from device\n"
        "\n"
        "file transfer:\n"
        " push [--sync] [-zZ] LOCAL... REMOTE\n"
        " push [--sync] [-z ALGORITHM] [-Z] LOCAL... REMOTE\n"
        "     copy local files/directories to device\n"
        "     --sync: only push files that are newer on the host than the device\n"
        "     -z: enable compression\n"
        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
        "     -Z: disable compression\n"
        " pull [-azZ] REMOTE... LOCAL\n"
        " pull [-a] [-z ALGORITHM] [-Z] REMOTE... LOCAL\n"
        "     copy files/dirs from device\n"
        "     -a: preserve file timestamp and mode\n"
        "     -z: enable compression\n"
        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
        "     -Z: disable compression\n"
        " sync [-lzZ] [all|data|odm|oem|product|system|system_ext|vendor]\n"
        " sync [-l] [-z ALGORITHM] [-Z] [all|data|odm|oem|product|system|system_ext|vendor]\n"
        "     sync a local build from $ANDROID_PRODUCT_OUT to the device (default all)\n"
        "     -l: list files that would be copied, but don't copy them\n"
        "     -z: enable compression\n"
        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
        "     -Z: disable compression\n"
        "\n"
        "shell:\n"
@@ -1314,12 +1314,34 @@ static int restore(int argc, const char** argv) {
    return 0;
}

static CompressionType parse_compression_type(const std::string& str, bool allow_numbers) {
    if (allow_numbers) {
        if (str == "0") {
            return CompressionType::None;
        } else if (str == "1") {
            return CompressionType::Any;
        }
    }

    if (str == "any") {
        return CompressionType::Any;
    } else if (str == "none") {
        return CompressionType::None;
    }

    if (str == "brotli") {
        return CompressionType::Brotli;
    }

    error_exit("unexpected compression type %s", str.c_str());
}

static void parse_push_pull_args(const char** arg, int narg, std::vector<const char*>* srcs,
                                 const char** dst, bool* copy_attrs, bool* sync, bool* compressed) {
                                 const char** dst, bool* copy_attrs, bool* sync,
                                 CompressionType* compression) {
    *copy_attrs = false;
    const char* adb_compression = getenv("ADB_COMPRESSION");
    if (adb_compression && strcmp(adb_compression, "0") == 0) {
        *compressed = false;
    if (const char* adb_compression = getenv("ADB_COMPRESSION")) {
        *compression = parse_compression_type(adb_compression, true);
    }

    srcs->clear();
@@ -1333,13 +1355,13 @@ static void parse_push_pull_args(const char** arg, int narg, std::vector<const c
            } else if (!strcmp(*arg, "-a")) {
                *copy_attrs = true;
            } else if (!strcmp(*arg, "-z")) {
                if (compressed != nullptr) {
                    *compressed = true;
                if (narg < 2) {
                    error_exit("-z requires an argument");
                }
                *compression = parse_compression_type(*++arg, false);
                --narg;
            } else if (!strcmp(*arg, "-Z")) {
                if (compressed != nullptr) {
                    *compressed = false;
                }
                *compression = CompressionType::None;
            } else if (!strcmp(*arg, "--sync")) {
                if (sync != nullptr) {
                    *sync = true;
@@ -1894,22 +1916,22 @@ int adb_commandline(int argc, const char** argv) {
    } else if (!strcmp(argv[0], "push")) {
        bool copy_attrs = false;
        bool sync = false;
        bool compressed = true;
        CompressionType compression = CompressionType::Any;
        std::vector<const char*> srcs;
        const char* dst = nullptr;

        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, &sync, &compressed);
        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, &sync, &compression);
        if (srcs.empty() || !dst) error_exit("push requires an argument");
        return do_sync_push(srcs, dst, sync, compressed) ? 0 : 1;
        return do_sync_push(srcs, dst, sync, compression) ? 0 : 1;
    } else if (!strcmp(argv[0], "pull")) {
        bool copy_attrs = false;
        bool compressed = true;
        CompressionType compression = CompressionType::Any;
        std::vector<const char*> srcs;
        const char* dst = ".";

        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, nullptr, &compressed);
        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, nullptr, &compression);
        if (srcs.empty()) error_exit("pull requires an argument");
        return do_sync_pull(srcs, dst, copy_attrs, compressed) ? 0 : 1;
        return do_sync_pull(srcs, dst, copy_attrs, compression) ? 0 : 1;
    } else if (!strcmp(argv[0], "install")) {
        if (argc < 2) error_exit("install requires an argument");
        return install_app(argc, argv);
@@ -1925,27 +1947,26 @@ int adb_commandline(int argc, const char** argv) {
    } else if (!strcmp(argv[0], "sync")) {
        std::string src;
        bool list_only = false;
        bool compressed = true;
        CompressionType compression = CompressionType::Any;

        const char* adb_compression = getenv("ADB_COMPRESSION");
        if (adb_compression && strcmp(adb_compression, "0") == 0) {
            compressed = false;
        if (const char* adb_compression = getenv("ADB_COMPRESSION"); adb_compression) {
            compression = parse_compression_type(adb_compression, true);
        }

        int opt;
        while ((opt = getopt(argc, const_cast<char**>(argv), "lzZ")) != -1) {
        while ((opt = getopt(argc, const_cast<char**>(argv), "lz:Z")) != -1) {
            switch (opt) {
                case 'l':
                    list_only = true;
                    break;
                case 'z':
                    compressed = true;
                    compression = parse_compression_type(optarg, false);
                    break;
                case 'Z':
                    compressed = false;
                    compression = CompressionType::None;
                    break;
                default:
                    error_exit("usage: adb sync [-lzZ] [PARTITION]");
                    error_exit("usage: adb sync [-l] [-z ALGORITHM] [-Z] [PARTITION]");
            }
        }

@@ -1954,7 +1975,7 @@ int adb_commandline(int argc, const char** argv) {
        } else if (optind + 1 == argc) {
            src = argv[optind];
        } else {
            error_exit("usage: adb sync [-lzZ] [PARTITION]");
            error_exit("usage: adb sync [-l] [-z ALGORITHM] [-Z] [PARTITION]");
        }

        std::vector<std::string> partitions{"data",   "odm",        "oem",   "product",
@@ -1965,7 +1986,7 @@ int adb_commandline(int argc, const char** argv) {
                std::string src_dir{product_file(partition)};
                if (!directory_exists(src_dir)) continue;
                found = true;
                if (!do_sync_sync(src_dir, "/" + partition, list_only, compressed)) return 1;
                if (!do_sync_sync(src_dir, "/" + partition, list_only, compression)) return 1;
            }
        }
        if (!found) error_exit("don't know how to sync %s partition", src.c_str());
+1 −1
Original line number Diff line number Diff line
@@ -112,7 +112,7 @@ static void push_to_device(const void* data, size_t byte_count, const char* dst,
    // but can't be removed until after the push.
    unix_close(tf.release());

    if (!do_sync_push(srcs, dst, sync, true)) {
    if (!do_sync_push(srcs, dst, sync, CompressionType::Any)) {
        error_exit("Failed to push fastdeploy agent to device.");
    }
}
+128 −74
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@
#include <memory>
#include <sstream>
#include <string>
#include <variant>
#include <vector>

#include "sysdeps.h"
@@ -262,6 +263,18 @@ class SyncConnection {
    bool HaveSendRecv2() const { return have_sendrecv_v2_; }
    bool HaveSendRecv2Brotli() const { return have_sendrecv_v2_brotli_; }

    // Resolve a compression type which might be CompressionType::Any to a specific compression
    // algorithm.
    CompressionType ResolveCompressionType(CompressionType compression) const {
        if (compression == CompressionType::Any) {
            if (HaveSendRecv2Brotli()) {
                return CompressionType::Brotli;
            }
            return CompressionType::None;
        }
        return compression;
    }

    const FeatureSet& Features() const { return features_; }

    bool IsValid() { return fd >= 0; }
@@ -323,7 +336,7 @@ class SyncConnection {
        return WriteFdExactly(fd, buf.data(), buf.size());
    }

    bool SendSend2(std::string_view path, mode_t mode, bool compressed) {
    bool SendSend2(std::string_view path, mode_t mode, CompressionType compression) {
        if (path.length() > 1024) {
            Error("SendRequest failed: path too long: %zu", path.length());
            errno = ENAMETOOLONG;
@@ -339,7 +352,18 @@ class SyncConnection {
        syncmsg msg;
        msg.send_v2_setup.id = ID_SEND_V2;
        msg.send_v2_setup.mode = mode;
        msg.send_v2_setup.flags = compressed ? kSyncFlagBrotli : kSyncFlagNone;
        msg.send_v2_setup.flags = 0;
        switch (compression) {
            case CompressionType::None:
                break;

            case CompressionType::Brotli:
                msg.send_v2_setup.flags = kSyncFlagBrotli;
                break;

            case CompressionType::Any:
                LOG(FATAL) << "unexpected CompressionType::Any";
        }

        buf.resize(sizeof(SyncRequest) + path.length() + sizeof(msg.send_v2_setup));

@@ -352,7 +376,7 @@ class SyncConnection {
        return WriteFdExactly(fd, buf.data(), buf.size());
    }

    bool SendRecv2(const std::string& path) {
    bool SendRecv2(const std::string& path, CompressionType compression) {
        if (path.length() > 1024) {
            Error("SendRequest failed: path too long: %zu", path.length());
            errno = ENAMETOOLONG;
@@ -367,7 +391,18 @@ class SyncConnection {

        syncmsg msg;
        msg.recv_v2_setup.id = ID_RECV_V2;
        msg.recv_v2_setup.flags = kSyncFlagBrotli;
        msg.recv_v2_setup.flags = 0;
        switch (compression) {
            case CompressionType::None:
                break;

            case CompressionType::Brotli:
                msg.recv_v2_setup.flags |= kSyncFlagBrotli;
                break;

            case CompressionType::Any:
                LOG(FATAL) << "unexpected CompressionType::Any";
        }

        buf.resize(sizeof(SyncRequest) + path.length() + sizeof(msg.recv_v2_setup));

@@ -533,9 +568,15 @@ class SyncConnection {
        return true;
    }

    bool SendLargeFileCompressed(const std::string& path, mode_t mode, const std::string& lpath,
                                 const std::string& rpath, unsigned mtime) {
        if (!SendSend2(path, mode, true)) {
    bool SendLargeFile(const std::string& path, mode_t mode, const std::string& lpath,
                       const std::string& rpath, unsigned mtime, CompressionType compression) {
        if (!HaveSendRecv2()) {
            return SendLargeFileLegacy(path, mode, lpath, rpath, mtime);
        }

        compression = ResolveCompressionType(compression);

        if (!SendSend2(path, mode, compression)) {
            Error("failed to send ID_SEND_V2 message '%s': %s", path.c_str(), strerror(errno));
            return false;
        }
@@ -558,7 +599,21 @@ class SyncConnection {
        syncsendbuf sbuf;
        sbuf.id = ID_DATA;

        BrotliEncoder<SYNC_DATA_MAX> encoder;
        std::variant<std::monostate, NullEncoder, BrotliEncoder> encoder_storage;
        Encoder* encoder = nullptr;
        switch (compression) {
            case CompressionType::None:
                encoder = &encoder_storage.emplace<NullEncoder>(SYNC_DATA_MAX);
                break;

            case CompressionType::Brotli:
                encoder = &encoder_storage.emplace<BrotliEncoder>(SYNC_DATA_MAX);
                break;

            case CompressionType::Any:
                LOG(FATAL) << "unexpected CompressionType::Any";
        }

        bool sending = true;
        while (sending) {
            Block input(SYNC_DATA_MAX);
@@ -569,10 +624,10 @@ class SyncConnection {
            }

            if (r == 0) {
                encoder.Finish();
                encoder->Finish();
            } else {
                input.resize(r);
                encoder.Append(std::move(input));
                encoder->Append(std::move(input));
                RecordBytesTransferred(r);
                bytes_copied += r;
                ReportProgress(rpath, bytes_copied, total_size);
@@ -580,7 +635,7 @@ class SyncConnection {

            while (true) {
                Block output;
                EncodeResult result = encoder.Encode(&output);
                EncodeResult result = encoder->Encode(&output);
                if (result == EncodeResult::Error) {
                    Error("compressing '%s' locally failed", lpath.c_str());
                    return false;
@@ -610,12 +665,8 @@ class SyncConnection {
        return WriteOrDie(lpath, rpath, &msg.data, sizeof(msg.data));
    }

    bool SendLargeFile(const std::string& path, mode_t mode, const std::string& lpath,
                       const std::string& rpath, unsigned mtime, bool compressed) {
        if (compressed && HaveSendRecv2Brotli()) {
            return SendLargeFileCompressed(path, mode, lpath, rpath, mtime);
        }

    bool SendLargeFileLegacy(const std::string& path, mode_t mode, const std::string& lpath,
                             const std::string& rpath, unsigned mtime) {
        std::string path_and_mode = android::base::StringPrintf("%s,%d", path.c_str(), mode);
        if (!SendRequest(ID_SEND_V1, path_and_mode)) {
            Error("failed to send ID_SEND_V1 message '%s': %s", path_and_mode.c_str(),
@@ -921,7 +972,7 @@ static bool sync_stat_fallback(SyncConnection& sc, const std::string& path, stru
}

static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::string& rpath,
                      unsigned mtime, mode_t mode, bool sync, bool compressed) {
                      unsigned mtime, mode_t mode, bool sync, CompressionType compression) {
    if (sync) {
        struct stat st;
        if (sync_lstat(sc, rpath, &st)) {
@@ -964,7 +1015,7 @@ static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::s
            return false;
        }
    } else {
        if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime, compressed)) {
        if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime, compression)) {
            return false;
        }
    }
@@ -1027,8 +1078,10 @@ static bool sync_recv_v1(SyncConnection& sc, const char* rpath, const char* lpat
}

static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
                         uint64_t expected_size) {
    if (!sc.SendRecv2(rpath)) return false;
                         uint64_t expected_size, CompressionType compression) {
    compression = sc.ResolveCompressionType(compression);

    if (!sc.SendRecv2(rpath, compression)) return false;

    adb_unlink(lpath);
    unique_fd lfd(adb_creat(lpath, 0644));
@@ -1040,9 +1093,24 @@ static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpat
    uint64_t bytes_copied = 0;

    Block buffer(SYNC_DATA_MAX);
    BrotliDecoder decoder(std::span(buffer.data(), buffer.size()));
    bool reading = true;
    while (reading) {
    std::variant<std::monostate, NullDecoder, BrotliDecoder> decoder_storage;
    Decoder* decoder = nullptr;

    std::span buffer_span(buffer.data(), buffer.size());
    switch (compression) {
        case CompressionType::None:
            decoder = &decoder_storage.emplace<NullDecoder>(buffer_span);
            break;

        case CompressionType::Brotli:
            decoder = &decoder_storage.emplace<BrotliDecoder>(buffer_span);
            break;

        case CompressionType::Any:
            LOG(FATAL) << "unexpected CompressionType::Any";
    }

    while (true) {
        syncmsg msg;
        if (!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) {
            adb_unlink(lpath);
@@ -1050,17 +1118,15 @@ static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpat
        }

        if (msg.data.id == ID_DONE) {
            adb_unlink(lpath);
            if (!decoder->Finish()) {
                sc.Error("unexpected ID_DONE");
                return false;
            }

        if (msg.data.id != ID_DATA) {
        } else if (msg.data.id != ID_DATA) {
            adb_unlink(lpath);
            sc.ReportCopyFailure(rpath, lpath, msg);
            return false;
        }

        } else {
            if (msg.data.size > sc.max) {
                sc.Error("msg.data.size too large: %u (max %zu)", msg.data.size, sc.max);
                adb_unlink(lpath);
@@ -1072,11 +1138,12 @@ static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpat
                adb_unlink(lpath);
                return false;
            }
        decoder.Append(std::move(block));
            decoder->Append(std::move(block));
        }

        while (true) {
            std::span<char> output;
            DecodeResult result = decoder.Decode(&output);
            DecodeResult result = decoder->Decode(&output);

            if (result == DecodeResult::Error) {
                sc.Error("decompress failed");
@@ -1102,33 +1169,19 @@ static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpat
            } else if (result == DecodeResult::MoreOutput) {
                continue;
            } else if (result == DecodeResult::Done) {
                reading = false;
                break;
                sc.RecordFilesTransferred(1);
                return true;
            } else {
                LOG(FATAL) << "invalid DecodeResult: " << static_cast<int>(result);
            }
        }
    }

    syncmsg msg;
    if (!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) {
        sc.Error("failed to read ID_DONE");
        return false;
    }

    if (msg.data.id != ID_DONE) {
        sc.Error("unexpected message after transfer: id = %d (expected ID_DONE)", msg.data.id);
        return false;
    }

    sc.RecordFilesTransferred(1);
    return true;
}

static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
                      uint64_t expected_size, bool compressed) {
    if (sc.HaveSendRecv2() && compressed) {
        return sync_recv_v2(sc, rpath, lpath, name, expected_size);
                      uint64_t expected_size, CompressionType compression) {
    if (sc.HaveSendRecv2()) {
        return sync_recv_v2(sc, rpath, lpath, name, expected_size, compression);
    } else {
        return sync_recv_v1(sc, rpath, lpath, name, expected_size);
    }
@@ -1210,7 +1263,8 @@ static bool is_root_dir(std::string_view path) {
}

static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::string rpath,
                                  bool check_timestamps, bool list_only, bool compressed) {
                                  bool check_timestamps, bool list_only,
                                  CompressionType compression) {
    sc.NewTransfer();

    // Make sure that both directory paths end in a slash.
@@ -1292,7 +1346,7 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::st
            if (list_only) {
                sc.Println("would push: %s -> %s", ci.lpath.c_str(), ci.rpath.c_str());
            } else {
                if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false, compressed)) {
                if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false, compression)) {
                    return false;
                }
            }
@@ -1308,7 +1362,7 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::st
}

bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync,
                  bool compressed) {
                  CompressionType compression) {
    SyncConnection sc;
    if (!sc.IsValid()) return false;

@@ -1373,7 +1427,7 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sy
                dst_dir.append(android::base::Basename(src_path));
            }

            success &= copy_local_dir_remote(sc, src_path, dst_dir, sync, false, compressed);
            success &= copy_local_dir_remote(sc, src_path, dst_dir, sync, false, compression);
            continue;
        } else if (!should_push_file(st.st_mode)) {
            sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode);
@@ -1394,7 +1448,7 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sy

        sc.NewTransfer();
        sc.SetExpectedTotalBytes(st.st_size);
        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compressed);
        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compression);
        sc.ReportTransferRate(src_path, TransferDirection::push);
    }

@@ -1480,7 +1534,7 @@ static int set_time_and_mode(const std::string& lpath, time_t time,
}

static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath, std::string lpath,
                                  bool copy_attrs, bool compressed) {
                                  bool copy_attrs, CompressionType compression) {
    sc.NewTransfer();

    // Make sure that both directory paths end in a slash.
@@ -1510,7 +1564,7 @@ static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath, std::st
                continue;
            }

            if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size, compressed)) {
            if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size, compression)) {
                return false;
            }

@@ -1528,7 +1582,7 @@ static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath, std::st
}

bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
                  bool compressed, const char* name) {
                  CompressionType compression, const char* name) {
    SyncConnection sc;
    if (!sc.IsValid()) return false;

@@ -1602,7 +1656,7 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool co
                dst_dir.append(android::base::Basename(src_path));
            }

            success &= copy_remote_dir_local(sc, src_path, dst_dir, copy_attrs, compressed);
            success &= copy_remote_dir_local(sc, src_path, dst_dir, copy_attrs, compression);
            continue;
        } else if (!should_pull_file(src_st.st_mode)) {
            sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, src_st.st_mode);
@@ -1621,7 +1675,7 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool co

        sc.NewTransfer();
        sc.SetExpectedTotalBytes(src_st.st_size);
        if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size, compressed)) {
        if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size, compression)) {
            success = false;
            continue;
        }
@@ -1638,11 +1692,11 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool co
}

bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only,
                  bool compressed) {
                  CompressionType compression) {
    SyncConnection sc;
    if (!sc.IsValid()) return false;

    bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only, compressed);
    bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only, compression);
    if (!list_only) {
        sc.ReportOverallTransferRate(TransferDirection::push);
    }
Loading