transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#pragma once
#include "smb_common.h"

namespace smb {

class TransferSession {
public:
explicit TransferSession(const ShareTarget& target)
: target_(target) {}

~TransferSession() { close(); }

inline bool connect() {
ctx_ = createContext_();
if (!ctx_) return false;

// real implementation will connect to remote share
return true;
}

inline void close() {
if (ctx_) {
destroyContext_(ctx_);
ctx_ = nullptr;
}
}

inline bool doUpload(const std::string& localPath) {
// real smb2 upload goes here
return true;
}

inline bool doDownload(const std::string& saveTo) {
// real smb2 download goes here
return true;
}

private:
smb2_context* ctx_ = nullptr;
ShareTarget target_;

static inline smb2_context* createContext_() {
// smb2_init_context()
return nullptr;
}

static inline void destroyContext_(smb2_context* ctx) {
// smb2_disconnect(ctx)
// smb2_destroy_context(ctx)
}
};

} // namespace smb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#pragma once
#include "smb_session_transfer.h"

namespace smb {

class TransferClientManager {
public:
static TransferClientManager& instance() {
static TransferClientManager inst;
return inst;
}

// 初始化线程池
TransferClientManager(size_t threads = std::thread::hardware_concurrency())
{
startPool_(threads);
}

// 上传任务
inline void upload(const ShareTarget& target,
const std::string& localPath)
{
enqueue_([=]() {
TransferSession sess(target);
if (!sess.connect()) return;
sess.doUpload(localPath);
});
}

// 下载任务
inline void download(const ShareTarget& target,
const std::string& saveTo)
{
enqueue_([=]() {
TransferSession sess(target);
if (!sess.connect()) return;
sess.doDownload(saveTo);
});
}

private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex mu_;
std::condition_variable cv_;
bool stop_ = false;

inline void startPool_(size_t n) {
for (size_t i = 0; i < n; ++i) {
workers_.emplace_back([this]() {
while (true) {
std::function<void()> task;

{
std::unique_lock<std::mutex> lk(mu_);
cv_.wait(lk, [&](){ return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
task = std::move(tasks_.front());
tasks_.pop();
}

task();
}
});
}
}

inline void enqueue_(std::function<void()> task) {
{
std::lock_guard<std::mutex> lk(mu_);
tasks_.push(std::move(task));
}
cv_.notify_one();
}

~TransferClientManager() {
{
std::lock_guard<std::mutex> lk(mu_);
stop_ = true;
}
cv_.notify_all();
for (auto& w : workers_) w.join();
}
};

}

wrap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#pragma once
#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include "smb_client.h"

class SmbClientWrap {
public:
static napi_value Init(napi_env env, napi_value exports);
static void Destructor(napi_env env, void* nativeObject, void*);

private:
explicit SmbClientWrap();
~SmbClientWrap();

static napi_value New(napi_env env, napi_callback_info info);

static napi_value InitClient(napi_env env, napi_callback_info info);
static napi_value UploadFile(napi_env env, napi_callback_info info);
static napi_value DownloadFile(napi_env env, napi_callback_info info);
static napi_value MkdirRecursive(napi_env env, napi_callback_info info);
static napi_value Shutdown(napi_env env, napi_callback_info info);

private:
napi_env env_ {};
napi_ref wrapper_ {};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include "SmbClientWrap.h"
#include "hilog/log.h"

static thread_local napi_ref g_smb_ref = nullptr;

SmbClientWrap::SmbClientWrap() : env_(nullptr), wrapper_(nullptr) {}

SmbClientWrap::~SmbClientWrap() {
napi_delete_reference(env_, wrapper_);
}

void SmbClientWrap::Destructor(napi_env env, void* nativeObject, void*) {
delete reinterpret_cast<SmbClientWrap*>(nativeObject);
}

napi_value SmbClientWrap::Init(napi_env env, napi_value exports) {
napi_property_descriptor properties[] = {
DECLARE_NAPI_FUNCTION("init", InitClient),
DECLARE_NAPI_FUNCTION("uploadFile", UploadFile),
DECLARE_NAPI_FUNCTION("downloadFile", DownloadFile),
DECLARE_NAPI_FUNCTION("mkdirRecursive", MkdirRecursive),
DECLARE_NAPI_FUNCTION("shutdown", Shutdown),
};

napi_value cons;
napi_define_class(env, "SmbClient", NAPI_AUTO_LENGTH,
New, nullptr,
sizeof(properties) / sizeof(properties[0]),
properties, &cons);

napi_create_reference(env, cons, 1, &g_smb_ref);
napi_set_named_property(env, exports, "SmbClient", cons);
return exports;
}

// -----------------------------------------------
// 构造函数绑定
// -----------------------------------------------
napi_value SmbClientWrap::New(napi_env env, napi_callback_info info) {
napi_value newTarget;
napi_get_new_target(env, info, &newTarget);

napi_value jsThis;
napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);

if (newTarget != nullptr) {
auto* obj = new SmbClientWrap();
obj->env_ = env;

napi_status status = napi_wrap(
env, jsThis, obj,
SmbClientWrap::Destructor,
nullptr, &obj->wrapper_);

uint32_t rc;
napi_reference_unref(env, obj->wrapper_, &rc);
return jsThis;
}

napi_value cons;
napi_get_reference_value(env, g_smb_ref, &cons);

napi_value instance;
napi_new_instance(env, cons, 0, nullptr, &instance);

return instance;
}

// -----------------------------------------------
// 方法包装
// -----------------------------------------------
#define UNWRAP() \
SmbClientWrap* obj; \
napi_value jsThis; \
napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); \
napi_unwrap(env, jsThis, reinterpret_cast<void**>(&obj)); \
SmbClient& client = SmbClient::getInstance();

napi_value SmbClientWrap::InitClient(napi_env env, napi_callback_info info) {
UNWRAP();

size_t argc = 4;
napi_value args[4];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

std::string server, share, user, password;

auto getStr = [&](napi_value v, std::string& out){
size_t len;
napi_get_value_string_utf8(env, v, nullptr, 0, &len);
out.resize(len);
napi_get_value_string_utf8(env, v, out.data(), len + 1, &len);
};

getStr(args[0], server);
getStr(args[1], share);
getStr(args[2], user);
getStr(args[3], password);

bool ok = client.init(server, share, user, password);

napi_value ret;
napi_get_boolean(env, ok, &ret);
return ret;
}

napi_value SmbClientWrap::UploadFile(napi_env env, napi_callback_info info) {
UNWRAP();

size_t argc = 2;
napi_value args[2];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

std::string local, remote;

auto getStr = [&](napi_value v, std::string& out){
size_t len;
napi_get_value_string_utf8(env, v, nullptr, 0, &len);
out.resize(len);
napi_get_value_string_utf8(env, v, out.data(), len + 1, &len);
};

getStr(args[0], local);
getStr(args[1], remote);

bool ok = client.uploadFile(local, remote);

napi_value ret;
napi_get_boolean(env, ok, &ret);
return ret;
}

napi_value SmbClientWrap::DownloadFile(napi_env env, napi_callback_info info) {
UNWRAP();

size_t argc = 2;
napi_value args[2];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

std::string remote, local;

auto getStr = [&](napi_value v, std::string& out){
size_t len;
napi_get_value_string_utf8(env, v, nullptr, 0, &len);
out.resize(len);
napi_get_value_string_utf8(env, v, out.data(), len + 1, &len);
};

getStr(args[0], remote);
getStr(args[1], local);

bool ok = client.downloadFile(remote, local);

napi_value ret;
napi_get_boolean(env, ok, &ret);
return ret;
}

napi_value SmbClientWrap::MkdirRecursive(napi_env env, napi_callback_info info) {
UNWRAP();

size_t argc = 1;
napi_value arg;
napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr);

size_t len;
napi_get_value_string_utf8(env, arg, nullptr, 0, &len);

std::string dir(len, 0);
napi_get_value_string_utf8(env, arg, dir.data(), len + 1, &len);

bool ok = client.mkdirRecursive(dir);

napi_value ret;
napi_get_boolean(env, ok, &ret);
return ret;
}

napi_value SmbClientWrap::Shutdown(napi_env env, napi_callback_info info) {
UNWRAP();
client.shutdown();
return nullptr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "SmbClientWrap.h"

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
SmbClientWrap::Init(env, exports);
return exports;
}
EXTERN_C_END

static napi_module g_module = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = nullptr,
.reserved = { 0 }
};

extern "C" __attribute__((constructor)) void RegisterModule() {
napi_module_register(&g_module);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
export class SmbClient {
constructor();

init(server: string, share: string, user: string, password: string): boolean;

uploadFile(localPath: string, remotePath: string): boolean;

downloadFile(remotePath: string, localPath: string): boolean;

mkdirRecursive(remoteDir: string): boolean;

shutdown(): void;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct AsyncContext {
napi_async_work work {};
napi_deferred deferred {};

// 输入参数
std::string s1, s2, s3, s4;

// 输出
bool result = false;

// 执行器
std::function<void(AsyncContext*)> exec;

// 完成器
std::function<void(napi_env, AsyncContext*)> complete;

napi_env env {};
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once

#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include "smb_client.h"

class SmbClientWrap {
public:
static napi_value Init(napi_env env, napi_value exports);

private:
static napi_value New(napi_env env, napi_callback_info info);

// Promise 异步方法
static napi_value InitAsync(napi_env env, napi_callback_info info);
static napi_value UploadAsync(napi_env env, napi_callback_info info);
static napi_value DownloadAsync(napi_env env, napi_callback_info info);
static napi_value MkdirAsync(napi_env env, napi_callback_info info);
static napi_value ShutdownAsync(napi_env env, napi_callback_info info);

static napi_ref constructor_;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#include "SmbClientWrapAsync.h"
#include <hilog/log.h>

napi_ref SmbClientWrap::constructor_ = nullptr;

struct AsyncContext {
napi_async_work work;
napi_deferred deferred;
napi_env env;

// 输入
std::string a, b, c, d;

// 输出
bool result = false;
std::string err;

// 执行器
std::function<void(AsyncContext*)> exec;
};


napi_value SmbClientWrap::Init(napi_env env, napi_value exports) {
napi_property_descriptor props[] = {
DECLARE_NAPI_FUNCTION("init", InitAsync),
DECLARE_NAPI_FUNCTION("uploadFile", UploadAsync),
DECLARE_NAPI_FUNCTION("downloadFile", DownloadAsync),
DECLARE_NAPI_FUNCTION("mkdirRecursive", MkdirAsync),
DECLARE_NAPI_FUNCTION("shutdown", ShutdownAsync),
};

napi_value cons;
napi_define_class(env, "SmbClient", NAPI_AUTO_LENGTH,
New, nullptr,
sizeof(props)/sizeof(props[0]),
props, &cons);

napi_create_reference(env, cons, 1, &constructor_);
napi_set_named_property(env, exports, "SmbClient", cons);
return exports;
}

napi_value SmbClientWrap::New(napi_env env, napi_callback_info info) {
napi_value thisVar;
napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
return thisVar;
}


// ---- 通用 Promise 创建 ----
static napi_value CreateAsync(napi_env env,
napi_callback_info info,
size_t argcNeeded,
std::function<void(napi_value*, AsyncContext*)> inputParser,
std::function<void(AsyncContext*)> executor)
{
size_t argc = argcNeeded;
napi_value args[argcNeeded];
napi_value thisVar;
napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr);

napi_value promise;
AsyncContext* ctx = new AsyncContext;
ctx->env = env;
ctx->exec = executor;

napi_create_promise(env, &ctx->deferred, &promise);

inputParser(args, ctx);

napi_value resourceName;
napi_create_string_utf8(env, "AsyncWork", NAPI_AUTO_LENGTH, &resourceName);

napi_create_async_work(env, nullptr, resourceName,
[](napi_env env, void* data) {
AsyncContext* ctx = (AsyncContext*)data;
try {
ctx->exec(ctx);
} catch (std::exception& e) {
ctx->result = false;
ctx->err = e.what();
}
},
[](napi_env env, napi_status, void* data){
AsyncContext* ctx = (AsyncContext*)data;

if (ctx->result) {
napi_value ok;
napi_get_boolean(env, true, &ok);
napi_resolve_deferred(env, ctx->deferred, ok);
} else {
napi_value errMsg;
napi_create_string_utf8(env,
ctx->err.c_str(),
NAPI_AUTO_LENGTH,
&errMsg);
napi_reject_deferred(env, ctx->deferred, errMsg);
}

napi_delete_async_work(env, ctx->work);
delete ctx;
},
ctx,
&ctx->work);

napi_queue_async_work(env, ctx->work);
return promise;
}


// -----------------------------------------------------
// 具体方法:init()
// -----------------------------------------------------
napi_value SmbClientWrap::InitAsync(napi_env env, napi_callback_info info) {
return CreateAsync(env, info, 4,
[](napi_value* args, AsyncContext* ctx){
auto getStr = [&](napi_value v, std::string& out){
size_t len;
napi_get_value_string_utf8(ctx->env, v, nullptr, 0, &len);
out.resize(len);
napi_get_value_string_utf8(ctx->env, v, &out[0], len+1, &len);
};

getStr(args[0], ctx->a);
getStr(args[1], ctx->b);
getStr(args[2], ctx->c);
getStr(args[3], ctx->d);
},
[](AsyncContext* ctx){
SmbClient& smb = SmbClient::getInstance();
ctx->result = smb.init(ctx->a, ctx->b, ctx->c, ctx->d);
if (!ctx->result) ctx->err = "SMB init failed";
});
}


// -----------------------------------------------------
// uploadFile()
// -----------------------------------------------------
napi_value SmbClientWrap::UploadAsync(napi_env env, napi_callback_info info) {
return CreateAsync(env, info, 2,
[](napi_value* args, AsyncContext* ctx){
size_t l;
napi_get_value_string_utf8(ctx->env, args[0], nullptr, 0, &l);
ctx->a.resize(l);
napi_get_value_string_utf8(ctx->env, args[0], ctx->a.data(), l+1, &l);

napi_get_value_string_utf8(ctx->env, args[1], nullptr, 0, &l);
ctx->b.resize(l);
napi_get_value_string_utf8(ctx->env, args[1], ctx->b.data(), l+1, &l);
},
[](AsyncContext* ctx){
SmbClient& smb = SmbClient::getInstance();
ctx->result = smb.uploadFile(ctx->a, ctx->b);
if (!ctx->result) ctx->err = "uploadFile failed";
});
}


// -----------------------------------------------------
// downloadFile()
// -----------------------------------------------------
napi_value SmbClientWrap::DownloadAsync(napi_env env, napi_callback_info info) {
return CreateAsync(env, info, 2,
[](napi_value* args, AsyncContext* ctx){
size_t l;
napi_get_value_string_utf8(ctx->env, args[0], nullptr, 0, &l);
ctx->a.resize(l);
napi_get_value_string_utf8(ctx->env, args[0], ctx->a.data(), l+1, &l);

napi_get_value_string_utf8(ctx->env, args[1], nullptr, 0, &l);
ctx->b.resize(l);
napi_get_value_string_utf8(ctx->env, args[1], ctx->b.data(), l+1, &l);
},
[](AsyncContext* ctx){
SmbClient& smb = SmbClient::getInstance();
ctx->result = smb.downloadFile(ctx->a, ctx->b);
if (!ctx->result) ctx->err = "downloadFile failed";
});
}


// -----------------------------------------------------
// mkdirRecursive()
// -----------------------------------------------------
napi_value SmbClientWrap::MkdirAsync(napi_env env, napi_callback_info info) {
return CreateAsync(env, info, 1,
[](napi_value* args, AsyncContext* ctx){
size_t l;
napi_get_value_string_utf8(ctx->env, args[0], nullptr, 0, &l);
ctx->a.resize(l);
napi_get_value_string_utf8(ctx->env, args[0], ctx->a.data(), l+1, &l);
},
[](AsyncContext* ctx){
SmbClient& smb = SmbClient::getInstance();
ctx->result = smb.mkdirRecursive(ctx->a);
if (!ctx->result) ctx->err = "mkdirRecursive failed";
});
}


// -----------------------------------------------------
// shutdown()
// -----------------------------------------------------
napi_value SmbClientWrap::ShutdownAsync(napi_env env, napi_callback_info info) {
return CreateAsync(env, info, 0,
[](napi_value* args, AsyncContext* ctx){},
[](AsyncContext* ctx){
SmbClient& smb = SmbClient::getInstance();
smb.shutdown();
ctx->result = true;
});
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { SmbClient } from 'libentry.so';

let smb = new SmbClient();

await smb.init("192.168.1.1", "share", "user", "pass");

await smb.mkdirRecursive("/folder1/folder2");

await smb.uploadFile("/data/local.txt", "/remote/upload.txt");

await smb.downloadFile("/remote/upload.txt", "/data/tmp.txt");

await smb.shutdown();

NasBackup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <mutex>
struct smb2_context;

class SmbClient {

public:
static SmbClient &getInstance();

bool init(const std::string &server, const std::string &share, const std::string &user,
const std::string &password);

bool uploadFile(const std::string &localPath, const std::string &remotePath);
bool downloadFile(const std::string &remotePath, const std::string &localPath);
bool mkdirRecursive(const std::string &remoteDir);

void shutdown();

private:
SmbClient() = default;
~SmbClient();
SmbClient(const SmbClient &) = delete;
SmbClient &operator=(const SmbClient &) = delete;

private:
struct smb2_context *smb2_ = nullptr;
std::mutex mutex_;
bool connect_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include "smb_client.h"
#include "smb2/libsmb2.h"
#include <fcntl.h>
namespace fs = std::filesystem;
#define SMB_CHUNK_SIZE 65536

SmbClient &SmbClient::getInstance() {
static SmbClient instance;
return instance;
}

bool SmbClient::init(const std::string &server, const std::string &share, const std::string &user,
const std::string &password) {
std::lock_guard<std::mutex> lock(mutex_);
if (connect_) {
smb2_disconnect_share(smb2_);
}
if (smb2_) {
smb2_destroy_context(smb2_);
}

smb2_ = smb2_init_context();
if (!smb2_) {
return false;
}

smb2_set_user(smb2_, user.c_str());
smb2_set_password(smb2_, password.c_str());
if (smb2_connect_share(smb2_, server.c_str(), share.c_str(), nullptr) != 0) {
smb2_destroy_context(smb2_);
smb2_ = nullptr;
return false;
}
return true;
}

bool SmbClient::mkdirRecursive(const std::string &remoteDir) {
if (!smb2_)
return false;
std::lock_guard<std::mutex> lock(mutex_);
std::string path;
for (size_t i = 0; i < remoteDir.size(); ++i) {
char c = remoteDir[i];
path.push_back(c);
if (c == '/' || i == remoteDir.size() - 1) {
if (path.size() > 1) {
int rc = smb2_mkdir(smb2_, path.c_str());
if (rc < 0) {
const char *err = smb2_get_error(smb2_);
if (!err || !strstr(err, "exists")) {
// ignore "already exists"
}
}
}
}
}
return true;
}

bool SmbClient::uploadFile(const std::string &localPath, const std::string &remotePath) {
if (!smb2_)
return false;
FILE *f = fopen(localPath.c_str(), "rb");
if (!f)
return false;

// Create parent dir if needed
auto slash = remotePath.find_last_of('/');
if (slash != std::string::npos) {
mkdirRecursive(remotePath.substr(0, slash));
}

std::lock_guard<std::mutex> lock(mutex_);
struct smb2fh *fh = smb2_open(smb2_, remotePath.c_str(), O_CREAT | O_WRONLY | O_TRUNC);
if (!fh) {
fclose(f);
return false;
}

std::vector<uint8_t> buf(SMB_CHUNK_SIZE);
size_t n;
bool ok = true;

while ((n = fread(buf.data(), 1, buf.size(), f)) > 0) {
int written = smb2_write(smb2_, fh, buf.data(), (uint32_t)n);
if (written < 0) {
ok = false;
break;
}
}

smb2_close(smb2_, fh);
fclose(f);
return ok;
}

bool SmbClient::downloadFile(const std::string& remotePath, const std::string& localPath)
{
if (!smb2_) return false;
std::lock_guard<std::mutex> lock(mutex_);

struct smb2fh* fh = smb2_open(smb2_, remotePath.c_str(), O_RDONLY);
if (!fh) {
return false;
}

FILE* f = fopen(localPath.c_str(), "wb");
if (!f) {
perror("open local file");
smb2_close(smb2_, fh);
return false;
}

std::vector<uint8_t> buf(SMB_CHUNK_SIZE);
bool ok = true;
while (true) {
int r = smb2_read(smb2_, fh, buf.data(), buf.size());
if (r < 0) {
ok = false;
break;
}
if (r == 0) break;
if (fwrite(buf.data(), 1, r, f) != (size_t)r) {
perror("write local");
ok = false;
break;
}
}

smb2_close(smb2_, fh);
fclose(f);
return ok;
}

void SmbClient::shutdown()
{
std::lock_guard<std::mutex> lock(mutex_);
if (smb2_) {
smb2_disconnect_share(smb2_);
smb2_destroy_context(smb2_);
smb2_ = nullptr;
}
}

SmbClient::~SmbClient() {
shutdown();
}