2020-03-18 23:32:57 +01:00

306 lines
8.6 KiB
C++

#include "FileTransferObject.h"
#include "../../logger.h"
#include <iostream>
#ifdef WIN32
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#endif
using namespace tc;
using namespace tc::ft;
using namespace std;
#ifdef NODEJS_API
TransferJSBufferTarget::TransferJSBufferTarget() {
log_allocate("TransferJSBufferTarget", this);
if(!this->_js_buffer.IsEmpty()) {
assert(v8::Isolate::GetCurrent());
this->_js_buffer.Reset();
}
}
TransferJSBufferTarget::~TransferJSBufferTarget() {
log_free("TransferJSBufferTarget", this);
}
bool TransferJSBufferTarget::initialize(std::string &error) {
return true; /* we've already have data */
}
void TransferJSBufferTarget::finalize() { }
uint64_t TransferJSBufferTarget::stream_index() const {
return this->_js_buffer_index;
}
error::value TransferJSBufferTarget::write_bytes(std::string &error, uint8_t *source, uint64_t length) {
uint64_t write_length = length;
if(length > this->_js_buffer_length - this->_js_buffer_index)
write_length = this->_js_buffer_length - this->_js_buffer_index;
if(write_length > 0) {
memcpy((char*) this->_js_buffer_source + this->_js_buffer_index, source, write_length);
this->_js_buffer_index += write_length;
}
if(write_length == 0)
return error::out_of_space;
return error::success;
}
NAN_METHOD(TransferJSBufferTarget::create_from_buffer) {
if(info.Length() != 1 || !info[0]->IsArrayBuffer()) {
Nan::ThrowError("invalid argument");
return;
}
auto buffer = info[0].As<v8::ArrayBuffer>();
auto instance = make_shared<TransferJSBufferTarget>();
instance->_js_buffer = v8::Global<v8::ArrayBuffer>(info.GetIsolate(), info[0].As<v8::ArrayBuffer>());
instance->_js_buffer_source = buffer->GetContents().Data();
instance->_js_buffer_length = buffer->GetContents().ByteLength();
instance->_js_buffer_index = 0;
auto object_wrap = new TransferObjectWrap(instance);
auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
info.GetReturnValue().Set(object);
}
TransferJSBufferSource::~TransferJSBufferSource() {
log_free("TransferJSBufferSource", this);
if(!this->_js_buffer.IsEmpty()) {
assert(v8::Isolate::GetCurrent());
this->_js_buffer.Reset();
}
}
TransferJSBufferSource::TransferJSBufferSource() {
log_allocate("TransferJSBufferSource", this);
}
bool TransferJSBufferSource::initialize(std::string &string) { return true; }
void TransferJSBufferSource::finalize() { }
uint64_t TransferJSBufferSource::stream_index() const {
return this->_js_buffer_index;
}
uint64_t TransferJSBufferSource::byte_length() const {
return this->_js_buffer_length;
}
error::value TransferJSBufferSource::read_bytes(std::string &error, uint8_t *target, uint64_t &length) {
auto org_length = length;
if(this->_js_buffer_index + length > this->_js_buffer_length)
length = this->_js_buffer_length - this->_js_buffer_index;
memcpy(target, (char*) this->_js_buffer_source + this->_js_buffer_index, length);
this->_js_buffer_index += length;
if(org_length != 0 && length == 0)
return error::out_of_space;
return error::success;
}
NAN_METHOD(TransferJSBufferSource::create_from_buffer) {
if(info.Length() != 1 || !info[0]->IsArrayBuffer()) {
Nan::ThrowError("invalid argument");
return;
}
auto buffer = info[0].As<v8::ArrayBuffer>();
auto instance = make_shared<TransferJSBufferSource>();
instance->_js_buffer = v8::Global<v8::ArrayBuffer>(info.GetIsolate(), info[0].As<v8::ArrayBuffer>());
instance->_js_buffer_source = buffer->GetContents().Data();
instance->_js_buffer_length = buffer->GetContents().ByteLength();
instance->_js_buffer_index = 0;
auto object_wrap = new TransferObjectWrap(instance);
auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
info.GetReturnValue().Set(object);
}
NAN_MODULE_INIT(TransferObjectWrap::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(TransferObjectWrap::NewInstance);
klass->SetClassName(Nan::New("TransferObjectWrap").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(TransferObjectWrap::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
void TransferObjectWrap::do_wrap(v8::Local<v8::Object> object) {
this->Wrap(object);
auto source = dynamic_pointer_cast<TransferSource>(this->target());
auto target = dynamic_pointer_cast<TransferTarget>(this->target());
auto direction = source ? "upload" : "download";
Nan::Set(object,
Nan::New<v8::String>("direction").ToLocalChecked(),
Nan::New<v8::String>(direction).ToLocalChecked()
);
Nan::Set(object,
Nan::New<v8::String>("name").ToLocalChecked(),
Nan::New<v8::String>(this->target()->name().c_str()).ToLocalChecked()
);
if(source) {
Nan::Set(object,
Nan::New<v8::String>("total_size").ToLocalChecked(),
Nan::New<v8::Number>(source->byte_length())
);
}
if(target) {
Nan::Set(object,
Nan::New<v8::String>("expected_size").ToLocalChecked(),
Nan::New<v8::Number>(target->expected_length())
);
}
}
#endif
TransferFileSource::TransferFileSource(std::string path, std::string name) : _path{std::move(path)}, _name{std::move(name)} {
if(!this->_path.empty()) {
if(this->_path.back() == '/')
this->_path.pop_back();
#ifdef WIN32
if(this->_path.back() == '\\')
this->_path.pop_back();
#endif
}
}
uint64_t TransferFileSource::byte_length() const {
if(file_size.has_value())
return file_size.value();
auto file = fs::u8path(this->_path) / fs::u8path(this->_name);
error_code error;
auto size = fs::file_size(file,error);
if(error)
size = 0;
return (this->file_size = std::make_optional<size_t>(size)).value();
}
bool TransferFileSource::initialize(std::string &error) {
auto file = fs::u8path(this->_path) / fs::u8path(this->_name);
log_debug(category::file_transfer, tr("Opening source file for transfer: {} ({})"), file.string(), fs::absolute(file).string());
error_code errc;
if(!fs::exists(file)) {
error = "file not found";
return false;
}
if(errc) {
error = "failed to test for file existence: " + to_string(errc.value()) + "/" + errc.message();
return false;
}
if(!fs::is_regular_file(file, errc)) {
error = "target file isn't a regular file";
return false;
}
if(errc) {
error = "failed to test for file regularity: " + to_string(errc.value()) + "/" + errc.message();
return false;
}
this->file_stream = std::ifstream{file, std::ifstream::in | std::ifstream::binary};
if(!this->file_stream) {
error = "failed to open file";
return false;
}
this->file_stream.seekg(0, std::ifstream::end);
auto length = this->file_stream.tellg();
if(length != this->byte_length()) {
error = "file length missmatch";
return false;
}
this->file_stream.seekg(0, std::ifstream::beg);
this->position = 0;
return true;
}
void TransferFileSource::finalize() {
if(this->file_stream)
this->file_stream.close();
this->position = 0;
}
error::value TransferFileSource::read_bytes(std::string &error, uint8_t *buffer, uint64_t &length) {
error.clear();
#ifdef WIN32
auto blength = this->byte_length();
if(this->position >= blength) {
error = "eof reached";
return error::custom;
}
if(this->position + length > this->byte_length())
length = this->byte_length() - this->position;
this->file_stream.read((char*) buffer, length);
this->position += length;
#else
auto result = this->file_stream.readsome((char*) buffer, length);
if(result > 0) {
length = result;
this->position += result;
return error::success;
} else if(result == 0) {
return error::would_block;
} else
error = "read returned " + to_string(result) + "/" + to_string(length);
#endif
if(!this->file_stream) {
if(this->file_stream.eof())
error = "eof reached";
else
error = "io error. failed to read";
}
return error.empty() ? error::success : error::custom;
}
uint64_t TransferFileSource::stream_index() const {
return this->position;
}
#ifdef NODEJS_API
NAN_METHOD(TransferFileSource::create) {
if(info.Length() != 2 || !info[0]->IsString() || !info[1]->IsString()) {
Nan::ThrowError("invalid argument");
return;
}
auto instance = make_shared<TransferFileSource>(*Nan::Utf8String{info[0]}, *Nan::Utf8String{info[1]});
auto object_wrap = new TransferObjectWrap(instance);
auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
info.GetReturnValue().Set(object);
}
#endif