Merge changes Icd4322f2,I441ae663

am: 0525134d89

Change-Id: Ia592ec1577686a176aef4732cbcc945a8de68c00
This commit is contained in:
Tianjie Xu authored and android-build-merger committed 2018-03-21 19:49:12 +00:00
commit 8ccc9ed515
11 files changed
+284 -96

No files matched your search

+1 -1
View File
@@ -30,7 +30,7 @@ class BrotliCompressor : public CompressorInterface {
bool Write(const uint8_t* buf, size_t size) override;
bool Finish() override;
const std::vector<uint8_t>& GetCompressedData() override;
CompressorType Type() override { return CompressorType::kBrotli; }
CompressorType Type() const override { return CompressorType::kBrotli; }
private:
BrotliEncoderState* brotli_encoder_state_;
+56 -25
View File
@@ -41,21 +41,34 @@ const uint32_t kBrotliDefaultQuality = BROTLI_MAX_QUALITY;
namespace bsdiff {
std::vector<CompressorType> BsdiffArguments::compressor_types() const {
return std::vector<CompressorType>(compressor_types_.begin(),
compressor_types_.end());
}
bool BsdiffArguments::IsValid() const {
if (compressor_type_ == CompressorType::kBrotli &&
if (compressor_types_.empty()) {
return false;
}
if (IsCompressorSupported(CompressorType::kBrotli) &&
(brotli_quality_ < BROTLI_MIN_QUALITY ||
brotli_quality_ > BROTLI_MAX_QUALITY)) {
return false;
}
if (format_ == BsdiffFormat::kLegacy) {
return compressor_type_ == CompressorType::kBZ2;
return compressor_types_.size() == 1 &&
IsCompressorSupported(CompressorType::kBZ2);
} else if (format_ == BsdiffFormat::kBsdf2) {
return (compressor_type_ == CompressorType::kBZ2 ||
compressor_type_ == CompressorType::kBrotli);
} else if (format_ == BsdiffFormat::kEndsley) {
// All compression options are valid for this format.
if (IsCompressorSupported(CompressorType::kNoCompression)) {
std::cerr << "no compression is not supported in Bsdf2 format\n";
return false;
}
return true;
} else if (format_ == BsdiffFormat::kEndsley) {
// Only one compressor is supported for this format.
return compressor_types_.size() == 1;
}
return false;
}
@@ -78,7 +91,7 @@ bool BsdiffArguments::ParseCommandLine(int argc, char** argv) {
return false;
}
} else if (name == "type") {
if (!ParseCompressorType(optarg, &compressor_type_)) {
if (!ParseCompressorTypes(optarg, &compressor_types_)) {
return false;
}
} else if (name == "brotli_quality") {
@@ -94,9 +107,9 @@ bool BsdiffArguments::ParseCommandLine(int argc, char** argv) {
// If quality is uninitialized for brotli, set it to default value.
if (format_ != BsdiffFormat::kLegacy &&
compressor_type_ == CompressorType::kBrotli && brotli_quality_ == -1) {
IsCompressorSupported(CompressorType::kBrotli) && brotli_quality_ == -1) {
brotli_quality_ = kBrotliDefaultQuality;
} else if (compressor_type_ != CompressorType::kBrotli &&
} else if (!IsCompressorSupported(CompressorType::kBrotli) &&
brotli_quality_ != -1) {
std::cerr << "Warning: Brotli quality is only used in the brotli"
" compressor.\n";
@@ -105,23 +118,37 @@ bool BsdiffArguments::ParseCommandLine(int argc, char** argv) {
return true;
}
bool BsdiffArguments::ParseCompressorType(const string& str,
CompressorType* type) {
string type_string = str;
std::transform(type_string.begin(), type_string.end(), type_string.begin(),
::tolower);
if (type_string == kNoCompressionString) {
*type = CompressorType::kNoCompression;
return true;
} else if (type_string == kBZ2String) {
*type = CompressorType::kBZ2;
return true;
} else if (type_string == kBrotliString) {
*type = CompressorType::kBrotli;
return true;
bool BsdiffArguments::ParseCompressorTypes(const string& str,
std::set<CompressorType>* types) {
types->clear();
// The expected types string is separated by ":", e.g. bz2:brotli
std::vector<std::string> type_list;
size_t base = 0;
size_t found;
while (true) {
found = str.find(":", base);
type_list.emplace_back(str, base, found - base);
if (found == str.npos)
break;
base = found + 1;
}
std::cerr << "Failed to parse compressor type in " << str << endl;
return false;
for (auto& type : type_list) {
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
if (type == kNoCompressionString) {
types->emplace(CompressorType::kNoCompression);
} else if (type == kBZ2String) {
types->emplace(CompressorType::kBZ2);
} else if (type == kBrotliString) {
types->emplace(CompressorType::kBrotli);
} else {
std::cerr << "Failed to parse compressor type in " << str << endl;
return false;
}
}
return true;
}
bool BsdiffArguments::ParseMinLength(const string& str, size_t* len) {
@@ -182,4 +209,8 @@ bool BsdiffArguments::ParseQuality(const string& str,
return true;
}
bool BsdiffArguments::IsCompressorSupported(CompressorType type) const {
return compressor_types_.find(type) != compressor_types_.end();
}
} // namespace bsdiff
+16 -10
View File
@@ -7,7 +7,9 @@
#include <stdint.h>
#include <set>
#include <string>
#include <vector>
#include "bsdiff/constants.h"
#include "bsdiff/patch_writer_interface.h"
@@ -18,14 +20,15 @@ namespace bsdiff {
// brotli_quality.
class BsdiffArguments {
public:
BsdiffArguments()
: format_(BsdiffFormat::kLegacy),
compressor_type_(CompressorType::kBZ2),
brotli_quality_(-1) {}
BsdiffArguments() : format_(BsdiffFormat::kLegacy), brotli_quality_(-1) {
compressor_types_.emplace(CompressorType::kBZ2);
}
BsdiffArguments(BsdiffFormat format, CompressorType type, int brotli_quality)
BsdiffArguments(BsdiffFormat format,
std::set<CompressorType> types,
int brotli_quality)
: format_(format),
compressor_type_(type),
compressor_types_(std::move(types)),
brotli_quality_(brotli_quality) {}
// Check if the compressor type is compatible with the bsdiff format.
@@ -36,7 +39,7 @@ class BsdiffArguments {
int min_length() const { return min_length_; }
CompressorType compressor_type() const { return compressor_type_; }
std::vector<CompressorType> compressor_types() const;
int brotli_quality() const { return brotli_quality_; }
@@ -45,7 +48,8 @@ class BsdiffArguments {
bool ParseCommandLine(int argc, char** argv);
// Parse the compression type from string.
static bool ParseCompressorType(const std::string& str, CompressorType* type);
static bool ParseCompressorTypes(const std::string& str,
std::set<CompressorType>* types);
// Parse the minimum length parameter from string.
static bool ParseMinLength(const std::string& str, size_t* len);
@@ -61,11 +65,13 @@ class BsdiffArguments {
int max);
private:
bool IsCompressorSupported(CompressorType type) const;
// Current format supported are the legacy "BSDIFF40" or "BSDF2".
BsdiffFormat format_;
// The algorithm to compress the patch, i.e. BZ2 or Brotli.
CompressorType compressor_type_;
// The algorithms to compress the patch, e.g. bz2, brotli.
std::set<CompressorType> compressor_types_;
// The quality of brotli compressor.
int brotli_quality_;
+41 -15
View File
@@ -4,21 +4,41 @@
#include "bsdiff/bsdiff_arguments.h"
#include <vector>
#include <gtest/gtest.h>
namespace bsdiff {
TEST(BsdiffArgumentsTest, ParseCompressorTypeTest) {
CompressorType type;
EXPECT_TRUE(BsdiffArguments::ParseCompressorType("Brotli", &type));
EXPECT_EQ(CompressorType::kBrotli, type);
std::set<CompressorType> types;
EXPECT_TRUE(BsdiffArguments::ParseCompressorTypes("Brotli", &types));
EXPECT_EQ(1U, types.size());
EXPECT_NE(types.end(), types.find(CompressorType::kBrotli));
EXPECT_TRUE(BsdiffArguments::ParseCompressorType("bz2", &type));
EXPECT_EQ(CompressorType::kBZ2, type);
types.clear();
EXPECT_FALSE(BsdiffArguments::ParseCompressorType("invalid", &type));
EXPECT_TRUE(BsdiffArguments::ParseCompressorTypes("bz2", &types));
EXPECT_EQ(1U, types.size());
EXPECT_NE(types.end(), types.find(CompressorType::kBZ2));
types.clear();
EXPECT_FALSE(BsdiffArguments::ParseCompressorTypes("invalid", &types));
}
TEST(BsdiffArgumentsTest, ParseMultipleCompressorTypeTest) {
std::set<CompressorType> types;
EXPECT_TRUE(BsdiffArguments::ParseCompressorTypes("bz2:brotli:nocompression",
&types));
EXPECT_EQ(3U, types.size());
EXPECT_NE(types.end(), types.find(CompressorType::kBrotli));
EXPECT_NE(types.end(), types.find(CompressorType::kBZ2));
EXPECT_NE(types.end(), types.find(CompressorType::kNoCompression));
types.clear();
// No space in the type string.
EXPECT_FALSE(
BsdiffArguments::ParseCompressorTypes("bz2 : nocompression", &types));
}
TEST(BsdiffArgumentsTest, ParseBsdiffFormatTest) {
@@ -66,28 +86,34 @@ TEST(BsdiffArgumentsTest, ArgumentsValidTest) {
// brotli is not supported for BsdiffFormat::kLegacy.
EXPECT_FALSE(
BsdiffArguments(BsdiffFormat::kLegacy, CompressorType::kBrotli, -1)
BsdiffArguments(BsdiffFormat::kLegacy, {CompressorType::kBrotli}, -1)
.IsValid());
EXPECT_TRUE(BsdiffArguments(BsdiffFormat::kBsdf2, CompressorType::kBrotli, 9)
.IsValid());
EXPECT_TRUE(
BsdiffArguments(BsdiffFormat::kBsdf2, {CompressorType::kBrotli}, 9)
.IsValid());
// Compression quality out of range for brotli.
EXPECT_FALSE(
BsdiffArguments(BsdiffFormat::kBsdf2, CompressorType::kBrotli, 20)
BsdiffArguments(BsdiffFormat::kBsdf2, {CompressorType::kBrotli}, 20)
.IsValid());
}
TEST(BsdiffArgumentsTest, ParseArgumentsSmokeTest) {
std::vector<const char*> args = {"bsdiff", "--format=bsdf2", "--type=brotli",
"--brotli_quality=9", "--minlen=12"};
std::vector<const char*> args = {"bsdiff", "--format=bsdf2",
"--type=brotli:bz2", "--brotli_quality=9",
"--minlen=12"};
BsdiffArguments arguments;
EXPECT_TRUE(
arguments.ParseCommandLine(args.size(), const_cast<char**>(args.data())));
EXPECT_EQ(BsdiffFormat::kBsdf2, arguments.format());
EXPECT_EQ(CompressorType::kBrotli, arguments.compressor_type());
std::vector<CompressorType> types = {CompressorType::kBZ2,
CompressorType::kBrotli};
EXPECT_EQ(types, arguments.compressor_types());
EXPECT_EQ(9, arguments.brotli_quality());
EXPECT_EQ(12, arguments.min_length());
}
+4 -3
View File
@@ -80,11 +80,11 @@ int GenerateBsdiffFromFiles(const char* old_filename,
patch_writer = bsdiff::CreateBsdiffPatchWriter(patch_filename);
} else if (arguments.format() == bsdiff::BsdiffFormat::kBsdf2) {
patch_writer = bsdiff::CreateBSDF2PatchWriter(patch_filename,
arguments.compressor_type(),
arguments.compressor_types(),
arguments.brotli_quality());
} else if (arguments.format() == bsdiff::BsdiffFormat::kEndsley) {
patch_writer = bsdiff::CreateEndsleyPatchWriter(
&raw_data, arguments.compressor_type(), arguments.brotli_quality());
&raw_data, arguments.compressor_types()[0], arguments.brotli_quality());
} else {
std::cerr << "unexpected bsdiff format." << std::endl;
return 1;
@@ -120,7 +120,8 @@ void PrintUsage(const std::string& proc_name) {
<< " --minlen LEN The minimum match length "
"required to consider a match in the algorithm.\n"
<< " --type <bz2|brotli|nocompression> The algorithm to compress "
"the patch, bsdf2 format only.\n"
"the patch, bsdf2 format only. Multiple supported compressors "
"should be split by ':', e.g. bz2:brotli.\n"
<< " --brotli_quality Quality of the brotli "
"compressor.\n";
}
+1 -1
View File
@@ -29,7 +29,7 @@ class BZ2Compressor : public CompressorInterface {
bool Write(const uint8_t* buf, size_t size) override;
bool Finish() override;
const std::vector<uint8_t>& GetCompressedData() override;
CompressorType Type() override { return CompressorType::kBZ2; }
CompressorType Type() const override { return CompressorType::kBZ2; }
private:
// The low-level bzip2 stream.
+1 -1
View File
@@ -29,7 +29,7 @@ class CompressorInterface {
virtual const std::vector<uint8_t>& GetCompressedData() = 0;
// Return the type of the current compressor.
virtual CompressorType Type() = 0;
virtual CompressorType Type() const = 0;
protected:
CompressorInterface() = default;
+8
View File
@@ -30,6 +30,14 @@ std::unique_ptr<PatchWriterInterface> CreateBSDF2PatchWriter(
CompressorType type,
int brotli_quality);
// Create a patch writer using the "BSDF2" patch format. It also tries all the
// compressors in |types| to generate the smallest patch.
BSDIFF_EXPORT
std::unique_ptr<PatchWriterInterface> CreateBSDF2PatchWriter(
const std::string& patch_filename,
const std::vector<CompressorType>& types,
int brotli_quality);
// Create a patch writer compatible with Android Play Store bsdiff patches.
// The data will be written to the passed |patch| vector, which must be valid
// until Close() is called or this patch is destroyed. The data will be
+118 -31
View File
@@ -5,6 +5,7 @@
#include "bsdiff/patch_writer.h"
#include <string.h>
#include <limits>
#include "bsdiff/brotli_compressor.h"
#include "bsdiff/bz2_compressor.h"
@@ -27,30 +28,64 @@ void EncodeInt64(int64_t x, uint8_t* buf) {
namespace bsdiff {
BsdiffPatchWriter::BsdiffPatchWriter(const std::string& patch_filename)
: patch_filename_(patch_filename), format_(BsdiffFormat::kLegacy) {
ctrl_stream_.reset(new BZ2Compressor());
diff_stream_.reset(new BZ2Compressor());
extra_stream_.reset(new BZ2Compressor());
: patch_filename_(patch_filename),
format_(BsdiffFormat::kLegacy),
brotli_quality_(-1) {
types_.emplace_back(CompressorType::kBZ2);
}
BsdiffPatchWriter::BsdiffPatchWriter(const std::string& patch_filename,
CompressorType type,
const std::vector<CompressorType>& types,
int brotli_quality)
: patch_filename_(patch_filename), format_(BsdiffFormat::kBsdf2) {
if (type == CompressorType::kBZ2) {
ctrl_stream_.reset(new BZ2Compressor());
diff_stream_.reset(new BZ2Compressor());
extra_stream_.reset(new BZ2Compressor());
} else if (type == CompressorType::kBrotli) {
ctrl_stream_.reset(new BrotliCompressor(brotli_quality));
diff_stream_.reset(new BrotliCompressor(brotli_quality));
extra_stream_.reset(new BrotliCompressor(brotli_quality));
: patch_filename_(patch_filename),
format_(BsdiffFormat::kBsdf2),
types_(types),
brotli_quality_(brotli_quality) {}
bool BsdiffPatchWriter::InitializeCompressorList(
std::vector<std::unique_ptr<bsdiff::CompressorInterface>>*
compressor_list) {
if (types_.empty()) {
LOG(ERROR) << "Patch writer expects at least one compressor.";
return false;
}
for (const auto& type : types_) {
switch (type) {
case CompressorType::kBZ2:
compressor_list->emplace_back(new BZ2Compressor());
break;
case CompressorType::kBrotli:
compressor_list->emplace_back(new BrotliCompressor(brotli_quality_));
break;
case CompressorType::kNoCompression:
LOG(ERROR) << "Unsupported compression type " << static_cast<int>(type);
}
}
for (const auto& compressor : *compressor_list) {
if (!compressor) {
return false;
}
}
return true;
}
bool BsdiffPatchWriter::Init(size_t /* new_size */) {
if (!(ctrl_stream_ && diff_stream_ && extra_stream_)) {
LOG(ERROR) << "uninitialized compressor stream";
if (!InitializeCompressorList(&ctrl_stream_list_)) {
LOG(ERROR) << "Failed to initialize control stream compressors.";
return false;
}
if (!InitializeCompressorList(&diff_stream_list_)) {
LOG(ERROR) << "Failed to initialize diff stream compressors.";
return false;
}
if (!InitializeCompressorList(&extra_stream_list_)) {
LOG(ERROR) << "Failed to initialize extra stream compressors.";
return false;
}
@@ -63,11 +98,23 @@ bool BsdiffPatchWriter::Init(size_t /* new_size */) {
}
bool BsdiffPatchWriter::WriteDiffStream(const uint8_t* data, size_t size) {
return diff_stream_->Write(data, size);
for (const auto& compressor : diff_stream_list_) {
if (!compressor->Write(data, size)) {
return false;
}
}
return true;
}
bool BsdiffPatchWriter::WriteExtraStream(const uint8_t* data, size_t size) {
return extra_stream_->Write(data, size);
for (const auto& compressor : extra_stream_list_) {
if (!compressor->Write(data, size)) {
return false;
}
}
return true;
}
bool BsdiffPatchWriter::AddControlEntry(const ControlEntry& entry) {
@@ -76,29 +123,69 @@ bool BsdiffPatchWriter::AddControlEntry(const ControlEntry& entry) {
EncodeInt64(entry.diff_size, buf);
EncodeInt64(entry.extra_size, buf + 8);
EncodeInt64(entry.offset_increment, buf + 16);
if (!ctrl_stream_->Write(buf, sizeof(buf)))
return false;
for (const auto& compressor : ctrl_stream_list_) {
if (!compressor->Write(buf, sizeof(buf))) {
return false;
}
}
written_output_ += entry.diff_size + entry.extra_size;
return true;
}
bool BsdiffPatchWriter::SelectSmallestResult(
const std::vector<std::unique_ptr<CompressorInterface>>& compressor_list,
CompressorInterface** smallest_compressor) {
size_t min_size = std::numeric_limits<size_t>::max();
for (const auto& compressor : compressor_list) {
if (!compressor->Finish()) {
LOG(ERROR) << "Failed to finalize compressed streams.";
return false;
}
// Update |smallest_compressor| if the current compressor produces a
// smaller result.
if (compressor->GetCompressedData().size() < min_size) {
min_size = compressor->GetCompressedData().size();
*smallest_compressor = compressor.get();
}
}
return true;
}
bool BsdiffPatchWriter::Close() {
if (!fp_) {
LOG(ERROR) << "File not open.";
return false;
}
if (!ctrl_stream_->Finish() || !diff_stream_->Finish() ||
!extra_stream_->Finish()) {
LOG(ERROR) << "Finalizing compressed streams.";
CompressorInterface* ctrl_stream = nullptr;
if (!SelectSmallestResult(ctrl_stream_list_, &ctrl_stream) || !ctrl_stream) {
return false;
}
auto ctrl_data = ctrl_stream_->GetCompressedData();
auto diff_data = diff_stream_->GetCompressedData();
auto extra_data = extra_stream_->GetCompressedData();
CompressorInterface* diff_stream = nullptr;
if (!SelectSmallestResult(diff_stream_list_, &diff_stream) || !diff_stream) {
return false;
}
if (!WriteHeader(ctrl_data.size(), diff_data.size()))
CompressorInterface* extra_stream = nullptr;
if (!SelectSmallestResult(extra_stream_list_, &extra_stream) ||
!extra_stream) {
return false;
}
auto ctrl_data = ctrl_stream->GetCompressedData();
auto diff_data = diff_stream->GetCompressedData();
auto extra_data = extra_stream->GetCompressedData();
uint8_t types[3] = {static_cast<uint8_t>(ctrl_stream->Type()),
static_cast<uint8_t>(diff_stream->Type()),
static_cast<uint8_t>(extra_stream->Type())};
if (!WriteHeader(types, ctrl_data.size(), diff_data.size()))
return false;
if (fwrite(ctrl_data.data(), 1, ctrl_data.size(), fp_) != ctrl_data.size()) {
@@ -122,7 +209,9 @@ bool BsdiffPatchWriter::Close() {
return true;
}
bool BsdiffPatchWriter::WriteHeader(uint64_t ctrl_size, uint64_t diff_size) {
bool BsdiffPatchWriter::WriteHeader(uint8_t types[3],
uint64_t ctrl_size,
uint64_t diff_size) {
/* Header format is
* 0 8 magic header
* 8 8 length of compressed ctrl block
@@ -146,9 +235,7 @@ bool BsdiffPatchWriter::WriteHeader(uint64_t ctrl_size, uint64_t diff_size) {
// 6 1 compressed type for diff stream
// 7 1 compressed type for extra stream
memcpy(header, kBSDF2MagicHeader, 5);
header[5] = static_cast<uint8_t>(ctrl_stream_->Type());
header[6] = static_cast<uint8_t>(diff_stream_->Type());
header[7] = static_cast<uint8_t>(extra_stream_->Type());
memcpy(header + 5, types, 3);
} else {
LOG(ERROR) << "Unsupported bsdiff format.";
return false;
+29 -8
View File
@@ -26,7 +26,7 @@ class BsdiffPatchWriter : public PatchWriterInterface {
// with algorithm |type|; and quality |brotli_quality| if it's brotli. This
// writer also writes the patch data to the file |patch_filename|.
BsdiffPatchWriter(const std::string& patch_filename,
CompressorType type,
const std::vector<CompressorType>& types,
int brotli_quality);
// PatchWriterInterface overrides.
@@ -37,9 +37,24 @@ class BsdiffPatchWriter : public PatchWriterInterface {
bool Close() override;
private:
// Write the BSDIFF patch header to the |fp_| given the size of the compressed
// control block and the compressed diff block.
bool WriteHeader(uint64_t ctrl_size, uint64_t diff_size);
// Add supported compressors to |compressor_list|; return false if we failed
// to initialize one of them.
bool InitializeCompressorList(
std::vector<std::unique_ptr<CompressorInterface>>* compressor_list);
// Select the compressor in |compressor_list| that produces the smallest
// patch, and put the result in |smallest_compressor|.
bool SelectSmallestResult(
const std::vector<std::unique_ptr<CompressorInterface>>& compressor_list,
CompressorInterface** smallest_compressor);
// Write the BSDIFF patch header to the |fp_|.
// Arguments:
// A three bytes array with the compressor types of ctrl|diff|extra stream
// Size of the compressed control block
// Size of the compressed diff block.
bool WriteHeader(uint8_t types[3], uint64_t ctrl_size, uint64_t diff_size);
// Bytes of the new files already written. Needed to store the new length in
// the header of the file.
@@ -52,10 +67,16 @@ class BsdiffPatchWriter : public PatchWriterInterface {
// The format of bsdiff we're using.
BsdiffFormat format_;
// The three internal compressed streams.
std::unique_ptr<CompressorInterface> ctrl_stream_{nullptr};
std::unique_ptr<CompressorInterface> diff_stream_{nullptr};
std::unique_ptr<CompressorInterface> extra_stream_{nullptr};
// The compressors we're using.
std::vector<CompressorType> types_;
// The compression quality of the brotli compressor.
int brotli_quality_;
// The list of compressors to try for each stream.
std::vector<std::unique_ptr<CompressorInterface>> ctrl_stream_list_;
std::vector<std::unique_ptr<CompressorInterface>> diff_stream_list_;
std::vector<std::unique_ptr<CompressorInterface>> extra_stream_list_;
};
} // namespace bsdiff
+9 -1
View File
@@ -19,8 +19,16 @@ std::unique_ptr<PatchWriterInterface> CreateBSDF2PatchWriter(
const std::string& patch_filename,
CompressorType type,
int brotli_quality) {
return CreateBSDF2PatchWriter(
patch_filename, std::vector<CompressorType>{type}, brotli_quality);
}
std::unique_ptr<PatchWriterInterface> CreateBSDF2PatchWriter(
const std::string& patch_filename,
const std::vector<CompressorType>& types,
int brotli_quality) {
return std::unique_ptr<PatchWriterInterface>(
new BsdiffPatchWriter(patch_filename, type, brotli_quality));
new BsdiffPatchWriter(patch_filename, types, brotli_quality));
}
std::unique_ptr<PatchWriterInterface> CreateEndsleyPatchWriter(