@cetapod/react-native-smb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +72 -0
- package/ReactNativeSmb.podspec +59 -0
- package/cpp/ios/OnLoad.mm +24 -0
- package/cpp/shared/HybridSMB.cpp +324 -0
- package/cpp/shared/HybridSMB.hpp +102 -0
- package/cpp/shared/ReactNativeSmb.hpp +428 -0
- package/cpp/shared/connection/ContextRequest.hpp +27 -0
- package/cpp/shared/connection/ContextRequestQueue.cpp +85 -0
- package/cpp/shared/connection/ContextRequestQueue.hpp +28 -0
- package/cpp/shared/connection/MetadataContextLease.hpp +33 -0
- package/cpp/shared/connection/PoolContextHandle.cpp +35 -0
- package/cpp/shared/connection/PoolContextHandle.hpp +44 -0
- package/cpp/shared/connection/PoolSlot.cpp +73 -0
- package/cpp/shared/connection/PoolSlot.hpp +46 -0
- package/cpp/shared/connection/PoolTypes.hpp +41 -0
- package/cpp/shared/connection/SmbConnection.cpp +385 -0
- package/cpp/shared/connection/SmbConnection.hpp +93 -0
- package/cpp/shared/connection/SmbConnectionPool.cpp +316 -0
- package/cpp/shared/connection/SmbConnectionPool.hpp +84 -0
- package/cpp/shared/core/CancellationToken.hpp +22 -0
- package/cpp/shared/core/OperatorContext.hpp +24 -0
- package/cpp/shared/core/SmbEnums.hpp +126 -0
- package/cpp/shared/core/SmbTask.cpp +131 -0
- package/cpp/shared/core/SmbTask.hpp +52 -0
- package/cpp/shared/core/SmbTaskCore.cpp +222 -0
- package/cpp/shared/core/SmbTaskCore.hpp +107 -0
- package/cpp/shared/core/SmbTypes.hpp +35 -0
- package/cpp/shared/core/TaskObserverHub.cpp +166 -0
- package/cpp/shared/core/TaskObserverHub.hpp +66 -0
- package/cpp/shared/core/TaskSnapshotCodec.cpp +32 -0
- package/cpp/shared/core/TaskSnapshotCodec.hpp +41 -0
- package/cpp/shared/io/SmbCopyTree.hpp +114 -0
- package/cpp/shared/io/SmbDirScan.hpp +80 -0
- package/cpp/shared/io/SmbFileIO.cpp +1355 -0
- package/cpp/shared/io/SmbFileIO.hpp +45 -0
- package/cpp/shared/io/SmbPathUtil.hpp +75 -0
- package/cpp/shared/io/SmbSecurity.cpp +308 -0
- package/cpp/shared/io/SmbSecurity.hpp +32 -0
- package/cpp/shared/io/SmbTreeOps.hpp +112 -0
- package/cpp/shared/operators/Operator.hpp +35 -0
- package/cpp/shared/operators/OperatorBase.cpp +57 -0
- package/cpp/shared/operators/OperatorBase.hpp +57 -0
- package/cpp/shared/operators/connection/ConnectOperator.cpp +19 -0
- package/cpp/shared/operators/connection/ConnectOperator.hpp +22 -0
- package/cpp/shared/operators/connection/ConnectShareOperator.cpp +19 -0
- package/cpp/shared/operators/connection/ConnectShareOperator.hpp +21 -0
- package/cpp/shared/operators/connection/DisconnectOperator.cpp +12 -0
- package/cpp/shared/operators/connection/DisconnectOperator.hpp +15 -0
- package/cpp/shared/operators/connection/InitializeOperator.cpp +12 -0
- package/cpp/shared/operators/connection/InitializeOperator.hpp +22 -0
- package/cpp/shared/operators/connection/ListSharesOperator.cpp +13 -0
- package/cpp/shared/operators/connection/ListSharesOperator.hpp +18 -0
- package/cpp/shared/operators/listing/GetPathInfoOperator.cpp +43 -0
- package/cpp/shared/operators/listing/GetPathInfoOperator.hpp +21 -0
- package/cpp/shared/operators/listing/GetSecurityDescriptorOperator.cpp +202 -0
- package/cpp/shared/operators/listing/GetSecurityDescriptorOperator.hpp +22 -0
- package/cpp/shared/operators/listing/ListDirectoryOperator.cpp +20 -0
- package/cpp/shared/operators/listing/ListDirectoryOperator.hpp +24 -0
- package/cpp/shared/operators/mutate/CopyItemOperator.cpp +19 -0
- package/cpp/shared/operators/mutate/CopyItemOperator.hpp +25 -0
- package/cpp/shared/operators/mutate/CreateDirectoryOperator.cpp +28 -0
- package/cpp/shared/operators/mutate/CreateDirectoryOperator.hpp +20 -0
- package/cpp/shared/operators/mutate/DeleteItemOperator.cpp +96 -0
- package/cpp/shared/operators/mutate/DeleteItemOperator.hpp +23 -0
- package/cpp/shared/operators/mutate/DuplicateItemOperator.cpp +48 -0
- package/cpp/shared/operators/mutate/DuplicateItemOperator.hpp +23 -0
- package/cpp/shared/operators/mutate/MoveItemOperator.cpp +32 -0
- package/cpp/shared/operators/mutate/MoveItemOperator.hpp +21 -0
- package/cpp/shared/operators/mutate/RenameItemOperator.cpp +37 -0
- package/cpp/shared/operators/mutate/RenameItemOperator.hpp +21 -0
- package/cpp/shared/operators/transfer/DownloadFileOperator.cpp +31 -0
- package/cpp/shared/operators/transfer/DownloadFileOperator.hpp +21 -0
- package/cpp/shared/operators/transfer/UploadFileOperator.cpp +27 -0
- package/cpp/shared/operators/transfer/UploadFileOperator.hpp +21 -0
- package/cpp/shared/util/SmbErrorMapper.hpp +229 -0
- package/cpp/shared/util/SmbLog.hpp +20 -0
- package/docs/API.md +105 -0
- package/docs/ARCHITECTURE.md +70 -0
- package/docs/TASK_MODEL.md +149 -0
- package/ios/include/smb2/libsmb2-dcerpc-lsa.h +172 -0
- package/ios/include/smb2/libsmb2-dcerpc-srvsvc.h +180 -0
- package/ios/include/smb2/libsmb2-dcerpc.h +155 -0
- package/ios/include/smb2/libsmb2-raw.h +497 -0
- package/ios/include/smb2/libsmb2.h +1384 -0
- package/ios/include/smb2/smb2-errors.h +549 -0
- package/ios/include/smb2/smb2.h +1251 -0
- package/ios/libs/device/libsmb2-device.a +0 -0
- package/ios/libs/libsmb2.xcframework/Info.plist +48 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/Headers/smb2/libsmb2-dcerpc-lsa.h +172 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/Headers/smb2/libsmb2-dcerpc-srvsvc.h +180 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/Headers/smb2/libsmb2-dcerpc.h +155 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/Headers/smb2/libsmb2-raw.h +497 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/Headers/smb2/libsmb2.h +1384 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/Headers/smb2/smb2-errors.h +549 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/Headers/smb2/smb2.h +1251 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64/libsmb2.a +0 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/Headers/smb2/libsmb2-dcerpc-lsa.h +172 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/Headers/smb2/libsmb2-dcerpc-srvsvc.h +180 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/Headers/smb2/libsmb2-dcerpc.h +155 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/Headers/smb2/libsmb2-raw.h +497 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/Headers/smb2/libsmb2.h +1384 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/Headers/smb2/smb2-errors.h +549 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/Headers/smb2/smb2.h +1251 -0
- package/ios/libs/libsmb2.xcframework/ios-arm64_x86_64-simulator/libsmb2.a +0 -0
- package/ios/libs/simulator/libsmb2-simulator.a +0 -0
- package/lib/ReactNativeSmb.d.ts +200 -0
- package/lib/ReactNativeSmb.js +1 -0
- package/lib/bridge/decode.d.ts +4 -0
- package/lib/bridge/decode.js +92 -0
- package/lib/core/SMB.d.ts +3 -0
- package/lib/core/SMB.js +4 -0
- package/lib/core/SmbClient.d.ts +35 -0
- package/lib/core/SmbClient.js +99 -0
- package/lib/core/SmbTask.d.ts +41 -0
- package/lib/core/SmbTask.js +74 -0
- package/lib/core/TransferStore.d.ts +22 -0
- package/lib/core/TransferStore.js +88 -0
- package/lib/core/ids.d.ts +1 -0
- package/lib/core/ids.js +3 -0
- package/lib/core/taskResult.d.ts +3 -0
- package/lib/core/taskResult.js +52 -0
- package/lib/hooks/useSubscribe.d.ts +20 -0
- package/lib/hooks/useSubscribe.js +36 -0
- package/lib/hooks/useTransfers.d.ts +7 -0
- package/lib/hooks/useTransfers.js +29 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +7 -0
- package/lib/labels.d.ts +6 -0
- package/lib/labels.js +71 -0
- package/lib/types.d.ts +141 -0
- package/lib/types.js +59 -0
- package/lib/utils.d.ts +79 -0
- package/lib/utils.js +153 -0
- package/package.json +63 -0
- package/scripts/build-libsmb2.sh +143 -0
- package/scripts/create-xcframework.sh +84 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <functional>
|
|
5
|
+
#include <future>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <unordered_map>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include <NitroModules/HybridObject.hpp>
|
|
12
|
+
#include <jsi/jsi.h>
|
|
13
|
+
|
|
14
|
+
#include "SmbTaskCore.hpp"
|
|
15
|
+
|
|
16
|
+
namespace react_native_smb {
|
|
17
|
+
|
|
18
|
+
class OperatorBase;
|
|
19
|
+
class SmbConnectionPool;
|
|
20
|
+
|
|
21
|
+
namespace jsi = facebook::jsi;
|
|
22
|
+
using namespace margelo::nitro;
|
|
23
|
+
|
|
24
|
+
class SmbTask : public HybridObject, public SmbTaskCore {
|
|
25
|
+
public:
|
|
26
|
+
SmbTask(std::string id, std::unique_ptr<OperatorBase> seedOp, SmbConnectionPool* pool);
|
|
27
|
+
~SmbTask();
|
|
28
|
+
|
|
29
|
+
void start();
|
|
30
|
+
size_t spawn(std::vector<std::unique_ptr<OperatorBase>> moreOps);
|
|
31
|
+
|
|
32
|
+
using SmbTaskCore::subscribe;
|
|
33
|
+
|
|
34
|
+
std::string getId() { return SmbTaskCore::getId(); }
|
|
35
|
+
void cancel();
|
|
36
|
+
std::unordered_map<std::string, std::string> get();
|
|
37
|
+
std::string subscribeMap(const std::function<void(const std::unordered_map<std::string, std::string>&)>& listener);
|
|
38
|
+
void unsubscribe(const std::string& id) { SmbTaskCore::unsubscribe(id); }
|
|
39
|
+
|
|
40
|
+
std::future<void> result() { return completionFuture(); }
|
|
41
|
+
|
|
42
|
+
private:
|
|
43
|
+
jsi::Value getResultValueRaw(jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* args, size_t count);
|
|
44
|
+
SmbConnectionPool* pool_{nullptr};
|
|
45
|
+
|
|
46
|
+
void launchOp(size_t opIndex);
|
|
47
|
+
size_t appendOps(std::vector<std::unique_ptr<OperatorBase>>& more);
|
|
48
|
+
|
|
49
|
+
void loadHybridMethods() override;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
} // namespace react_native_smb
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#include "SmbTaskCore.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <chrono>
|
|
5
|
+
|
|
6
|
+
#include "../operators/OperatorBase.hpp"
|
|
7
|
+
#include "../util/SmbLog.hpp"
|
|
8
|
+
|
|
9
|
+
namespace react_native_smb {
|
|
10
|
+
|
|
11
|
+
namespace jsi = facebook::jsi;
|
|
12
|
+
|
|
13
|
+
int64_t SmbTaskCore::nowMs() {
|
|
14
|
+
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
15
|
+
std::chrono::system_clock::now().time_since_epoch())
|
|
16
|
+
.count();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
SmbTaskCore::SmbTaskCore(std::string id, SmbOperatorKind kind)
|
|
20
|
+
: id_(std::move(id)), kind_(kind) {}
|
|
21
|
+
|
|
22
|
+
SmbTaskCore::~SmbTaskCore() = default;
|
|
23
|
+
|
|
24
|
+
void SmbTaskCore::cancel() {
|
|
25
|
+
cancel_.cancel();
|
|
26
|
+
settle(SmbTaskStatus::Cancelled, "", static_cast<int>(SmbErrorCode::Cancelled));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
SmbTaskState SmbTaskCore::getState() const {
|
|
30
|
+
SmbTaskState s;
|
|
31
|
+
writeToSnapshot(s);
|
|
32
|
+
return s;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void SmbTaskCore::setPaths(std::string source, std::string destination) {
|
|
36
|
+
{
|
|
37
|
+
std::lock_guard<std::mutex> lk(stateMutex_);
|
|
38
|
+
sourcePath_ = std::move(source);
|
|
39
|
+
destinationPath_ = std::move(destination);
|
|
40
|
+
updatedAt_ = nowMs();
|
|
41
|
+
}
|
|
42
|
+
notifyListeners();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void SmbTaskCore::addExpectedBytes(int64_t bytes) {
|
|
46
|
+
totalBytes_.fetch_add(bytes);
|
|
47
|
+
notifyListeners();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void SmbTaskCore::onOpStatus(size_t opIndex, SmbTaskStatus s, const std::string& error, int code) {
|
|
51
|
+
if (settled_.load(std::memory_order_acquire) && s == SmbTaskStatus::Success) return;
|
|
52
|
+
if (status_.load() == SmbTaskStatus::Cancelled && s == SmbTaskStatus::Success) return;
|
|
53
|
+
{
|
|
54
|
+
std::lock_guard<std::mutex> lk(stateMutex_);
|
|
55
|
+
if (opIndex < opProgress_.size()) opProgress_[opIndex].status = s;
|
|
56
|
+
if (s == SmbTaskStatus::Error && status_.load() != SmbTaskStatus::Error) {
|
|
57
|
+
errorMessage_ = error;
|
|
58
|
+
errorCode_ = code;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
notifyListeners();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void SmbTaskCore::onOpProgress(size_t opIndex, double done, double total) {
|
|
65
|
+
{
|
|
66
|
+
std::lock_guard<std::mutex> lk(stateMutex_);
|
|
67
|
+
if (opIndex < opProgress_.size()) {
|
|
68
|
+
opProgress_[opIndex].done = static_cast<int64_t>(done);
|
|
69
|
+
opProgress_[opIndex].total = static_cast<int64_t>(total);
|
|
70
|
+
}
|
|
71
|
+
recomputeAggregateLocked();
|
|
72
|
+
}
|
|
73
|
+
notifyListeners();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
std::string SmbTaskCore::subscribe(SnapshotListener listener) {
|
|
77
|
+
std::lock_guard<std::mutex> lk(listenersMutex_);
|
|
78
|
+
std::string id = "lst_" + std::to_string(nextListenerId_++);
|
|
79
|
+
listeners_[id] = std::move(listener);
|
|
80
|
+
return id;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void SmbTaskCore::unsubscribe(const std::string& id) {
|
|
84
|
+
std::lock_guard<std::mutex> lk(listenersMutex_);
|
|
85
|
+
listeners_.erase(id);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void SmbTaskCore::publishResult(std::any value,
|
|
89
|
+
std::function<jsi::Value(jsi::Runtime&, const std::any&)> converter) {
|
|
90
|
+
resultValue_ = std::move(value);
|
|
91
|
+
resultConverter_ = std::move(converter);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
jsi::Value SmbTaskCore::getResultValue(jsi::Runtime& runtime) const {
|
|
95
|
+
if (!resultConverter_) return jsi::Value::undefined();
|
|
96
|
+
try {
|
|
97
|
+
return resultConverter_(runtime, resultValue_);
|
|
98
|
+
} catch (...) {
|
|
99
|
+
return jsi::Value::undefined();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
std::future<void> SmbTaskCore::completionFuture() { return done_.get_future(); }
|
|
104
|
+
|
|
105
|
+
void SmbTaskCore::markPending() {
|
|
106
|
+
if (settled_.load(std::memory_order_acquire)) return;
|
|
107
|
+
status_ = SmbTaskStatus::Pending;
|
|
108
|
+
if (startedAt_ == 0) startedAt_ = nowMs();
|
|
109
|
+
updatedAt_ = nowMs();
|
|
110
|
+
notifyListeners();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
void SmbTaskCore::markRunning() {
|
|
114
|
+
if (settled_.load(std::memory_order_acquire)) return;
|
|
115
|
+
status_ = SmbTaskStatus::Running;
|
|
116
|
+
if (startedAt_ == 0) startedAt_ = nowMs();
|
|
117
|
+
updatedAt_ = nowMs();
|
|
118
|
+
notifyListeners();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
void SmbTaskCore::onOpFinished() {
|
|
122
|
+
const size_t before = pending_.fetch_sub(1);
|
|
123
|
+
if (before == 1) maybeSettle();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void SmbTaskCore::recomputeAggregateLocked() {
|
|
127
|
+
updatedAt_ = nowMs();
|
|
128
|
+
if (!isDeterminate(kind_)) {
|
|
129
|
+
progress_ = 0.0;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
int64_t d = 0, t = 0;
|
|
133
|
+
for (const auto& p : opProgress_) {
|
|
134
|
+
d += p.done;
|
|
135
|
+
t += p.total;
|
|
136
|
+
}
|
|
137
|
+
bytesDone_ = d;
|
|
138
|
+
const int64_t denom = std::max<int64_t>(t, std::max<int64_t>(d, totalBytes_.load()));
|
|
139
|
+
totalBytes_ = denom;
|
|
140
|
+
progress_ = (denom > 0) ? (static_cast<double>(d) / static_cast<double>(denom)) : 0.0;
|
|
141
|
+
|
|
142
|
+
const int64_t elapsedMs = updatedAt_ - startedAt_;
|
|
143
|
+
if (elapsedMs > 0 && d > 0) {
|
|
144
|
+
const double elapsedSec = elapsedMs / 1000.0;
|
|
145
|
+
bytesPerSecond_ = static_cast<double>(d) / elapsedSec;
|
|
146
|
+
etaSeconds_ = (denom > d && bytesPerSecond_ > 0.0) ? (static_cast<double>(denom - d) / bytesPerSecond_) : 0.0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
void SmbTaskCore::writeToSnapshot(SmbTaskState& out) const {
|
|
151
|
+
std::lock_guard<std::mutex> lk(stateMutex_);
|
|
152
|
+
out.taskId = id_;
|
|
153
|
+
out.kind = kind_;
|
|
154
|
+
out.status = status_.load();
|
|
155
|
+
out.progress = progress_.load();
|
|
156
|
+
out.errorCode = errorCode_.load();
|
|
157
|
+
out.errorMessage = errorMessage_;
|
|
158
|
+
out.sourcePath = sourcePath_;
|
|
159
|
+
out.destinationPath = destinationPath_;
|
|
160
|
+
out.bytesDone = bytesDone_.load();
|
|
161
|
+
out.totalBytes = totalBytes_.load();
|
|
162
|
+
out.bytesPerSecond = bytesPerSecond_;
|
|
163
|
+
out.etaSeconds = etaSeconds_;
|
|
164
|
+
out.startedAt = startedAt_;
|
|
165
|
+
out.updatedAt = updatedAt_;
|
|
166
|
+
out.endedAt = endedAt_;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
void SmbTaskCore::notifyListeners() {
|
|
170
|
+
SmbTaskState snap;
|
|
171
|
+
writeToSnapshot(snap);
|
|
172
|
+
std::lock_guard<std::mutex> lk(listenersMutex_);
|
|
173
|
+
for (auto& [id, fn] : listeners_) {
|
|
174
|
+
if (fn) fn(snap);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void SmbTaskCore::maybeSettle() {
|
|
179
|
+
if (cancel_.cancelled()) return;
|
|
180
|
+
const auto s = status_.load();
|
|
181
|
+
// Running: normal IO ops. Idle: pool-direct ops (initialize/connect/listShares) that never
|
|
182
|
+
// pass through OperatorBase::requestContext and therefore never call markPending/markRunning.
|
|
183
|
+
if (s == SmbTaskStatus::Running || s == SmbTaskStatus::Idle) {
|
|
184
|
+
settle(SmbTaskStatus::Success, "", static_cast<int>(SmbErrorCode::Unknown));
|
|
185
|
+
} else {
|
|
186
|
+
bool expected = false;
|
|
187
|
+
if (settled_.compare_exchange_strong(expected, true)) {
|
|
188
|
+
{
|
|
189
|
+
std::lock_guard<std::mutex> lk(stateMutex_);
|
|
190
|
+
endedAt_ = nowMs();
|
|
191
|
+
}
|
|
192
|
+
notifyListeners();
|
|
193
|
+
done_.set_value();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
void SmbTaskCore::settle(SmbTaskStatus finalStatus, const std::string& error, int code) {
|
|
199
|
+
// First settle wins — later op throws or emitStatus cannot override terminal state.
|
|
200
|
+
bool expected = false;
|
|
201
|
+
if (!settled_.compare_exchange_strong(expected, true)) return;
|
|
202
|
+
SMB_LOG("Task settle id=%s status=%s code=%d%s%s", id_.c_str(), statusName(finalStatus), code,
|
|
203
|
+
error.empty() ? "" : " error=", error.empty() ? "" : error.c_str());
|
|
204
|
+
|
|
205
|
+
status_ = finalStatus;
|
|
206
|
+
{
|
|
207
|
+
std::lock_guard<std::mutex> lk(stateMutex_);
|
|
208
|
+
if (!error.empty()) errorMessage_ = error;
|
|
209
|
+
errorCode_ = code;
|
|
210
|
+
endedAt_ = nowMs();
|
|
211
|
+
updatedAt_ = endedAt_;
|
|
212
|
+
if (finalStatus == SmbTaskStatus::Success) {
|
|
213
|
+
if (isDeterminate(kind_) && totalBytes_.load() > 0) bytesDone_ = totalBytes_.load();
|
|
214
|
+
progress_ = isDeterminate(kind_) ? 1.0 : 0.0;
|
|
215
|
+
etaSeconds_ = 0.0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
notifyListeners();
|
|
219
|
+
done_.set_value();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
} // namespace react_native_smb
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <any>
|
|
4
|
+
#include <atomic>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <functional>
|
|
7
|
+
#include <future>
|
|
8
|
+
#include <map>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
#include <jsi/jsi.h>
|
|
14
|
+
|
|
15
|
+
#include "CancellationToken.hpp"
|
|
16
|
+
#include "SmbEnums.hpp"
|
|
17
|
+
#include "SmbTypes.hpp"
|
|
18
|
+
|
|
19
|
+
namespace react_native_smb {
|
|
20
|
+
|
|
21
|
+
class OperatorBase;
|
|
22
|
+
|
|
23
|
+
namespace jsi = facebook::jsi;
|
|
24
|
+
|
|
25
|
+
// Thread-safe state, result storage, and subscriber fan-out. No HybridObject,
|
|
26
|
+
// no pool orchestration.
|
|
27
|
+
class SmbTaskCore {
|
|
28
|
+
public:
|
|
29
|
+
SmbTaskCore(std::string id, SmbOperatorKind kind);
|
|
30
|
+
~SmbTaskCore();
|
|
31
|
+
|
|
32
|
+
void cancel();
|
|
33
|
+
SmbTaskState getState() const;
|
|
34
|
+
std::string getId() const { return id_; }
|
|
35
|
+
SmbTaskStatus getStatus() const { return status_.load(); }
|
|
36
|
+
CancellationToken token() const { return cancel_; }
|
|
37
|
+
|
|
38
|
+
void setPaths(std::string source, std::string destination);
|
|
39
|
+
void addExpectedBytes(int64_t bytes);
|
|
40
|
+
|
|
41
|
+
void onOpStatus(size_t opIndex, SmbTaskStatus s, const std::string& error, int code);
|
|
42
|
+
void onOpProgress(size_t opIndex, double done, double total);
|
|
43
|
+
|
|
44
|
+
void publishResult(std::any value, std::function<jsi::Value(jsi::Runtime&, const std::any&)> converter);
|
|
45
|
+
jsi::Value getResultValue(jsi::Runtime& runtime) const;
|
|
46
|
+
|
|
47
|
+
std::string subscribe(SnapshotListener listener);
|
|
48
|
+
void unsubscribe(const std::string& id);
|
|
49
|
+
|
|
50
|
+
std::future<void> completionFuture();
|
|
51
|
+
|
|
52
|
+
void markPending();
|
|
53
|
+
void markRunning();
|
|
54
|
+
|
|
55
|
+
protected:
|
|
56
|
+
struct OpProgress {
|
|
57
|
+
int64_t done{0};
|
|
58
|
+
int64_t total{0};
|
|
59
|
+
SmbTaskStatus status{SmbTaskStatus::Idle};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
std::vector<std::unique_ptr<OperatorBase>> ops_;
|
|
63
|
+
std::vector<OpProgress> opProgress_;
|
|
64
|
+
std::atomic<size_t> pending_{0};
|
|
65
|
+
CancellationToken cancel_;
|
|
66
|
+
|
|
67
|
+
void onOpFinished();
|
|
68
|
+
void settle(SmbTaskStatus finalStatus, const std::string& error, int code);
|
|
69
|
+
|
|
70
|
+
static int64_t nowMs();
|
|
71
|
+
|
|
72
|
+
mutable std::mutex stateMutex_;
|
|
73
|
+
|
|
74
|
+
private:
|
|
75
|
+
std::string id_;
|
|
76
|
+
SmbOperatorKind kind_{SmbOperatorKind::Initialize};
|
|
77
|
+
std::atomic<SmbTaskStatus> status_{SmbTaskStatus::Idle};
|
|
78
|
+
std::atomic<double> progress_{0.0};
|
|
79
|
+
std::atomic<int> errorCode_{0};
|
|
80
|
+
std::string errorMessage_;
|
|
81
|
+
std::string sourcePath_;
|
|
82
|
+
std::string destinationPath_;
|
|
83
|
+
std::atomic<int64_t> bytesDone_{0};
|
|
84
|
+
std::atomic<int64_t> totalBytes_{0};
|
|
85
|
+
double bytesPerSecond_{0.0};
|
|
86
|
+
double etaSeconds_{0.0};
|
|
87
|
+
int64_t startedAt_{0};
|
|
88
|
+
int64_t updatedAt_{0};
|
|
89
|
+
int64_t endedAt_{0};
|
|
90
|
+
|
|
91
|
+
std::promise<void> done_;
|
|
92
|
+
std::atomic<bool> settled_{false};
|
|
93
|
+
|
|
94
|
+
std::map<std::string, SnapshotListener> listeners_;
|
|
95
|
+
mutable std::mutex listenersMutex_;
|
|
96
|
+
std::atomic<size_t> nextListenerId_{1};
|
|
97
|
+
|
|
98
|
+
std::any resultValue_;
|
|
99
|
+
std::function<jsi::Value(jsi::Runtime&, const std::any&)> resultConverter_;
|
|
100
|
+
|
|
101
|
+
void writeToSnapshot(SmbTaskState& out) const;
|
|
102
|
+
void notifyListeners();
|
|
103
|
+
void recomputeAggregateLocked();
|
|
104
|
+
void maybeSettle();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
} // namespace react_native_smb
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <functional>
|
|
5
|
+
#include <string>
|
|
6
|
+
|
|
7
|
+
#include "SmbEnums.hpp"
|
|
8
|
+
|
|
9
|
+
namespace react_native_smb {
|
|
10
|
+
|
|
11
|
+
// Immutable snapshot a task broadcasts to its subscribers. Pure data (DTO).
|
|
12
|
+
// Projected to/from a Nitro string map by TaskSnapshotCodec.
|
|
13
|
+
struct SmbTaskState {
|
|
14
|
+
std::string taskId;
|
|
15
|
+
SmbOperatorKind kind{SmbOperatorKind::Initialize};
|
|
16
|
+
SmbTaskStatus status{SmbTaskStatus::Idle};
|
|
17
|
+
double progress{0.0}; // 0.0 - 1.0 (meaningful only when isDeterminate(kind))
|
|
18
|
+
int errorCode{0};
|
|
19
|
+
std::string errorMessage;
|
|
20
|
+
std::string sourcePath;
|
|
21
|
+
std::string destinationPath;
|
|
22
|
+
int64_t bytesDone{0};
|
|
23
|
+
int64_t totalBytes{0};
|
|
24
|
+
double bytesPerSecond{0.0};
|
|
25
|
+
double etaSeconds{0.0};
|
|
26
|
+
int64_t startedAt{0};
|
|
27
|
+
int64_t updatedAt{0};
|
|
28
|
+
int64_t endedAt{0}; // 0 if not settled
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Any subscriber (JS bridge, observer hub, anyone). The task notifies all
|
|
32
|
+
// subscribers without knowing who they are.
|
|
33
|
+
using SnapshotListener = std::function<void(const SmbTaskState&)>;
|
|
34
|
+
|
|
35
|
+
} // namespace react_native_smb
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#include "TaskObserverHub.hpp"
|
|
2
|
+
|
|
3
|
+
#include "SmbTask.hpp"
|
|
4
|
+
|
|
5
|
+
namespace react_native_smb {
|
|
6
|
+
|
|
7
|
+
TaskObserverHub::~TaskObserverHub() {
|
|
8
|
+
// Unsubscribe from any still-alive tasks.
|
|
9
|
+
std::unique_lock lock(mutex_);
|
|
10
|
+
for (auto& [taskId, weak] : tracked_) {
|
|
11
|
+
if (auto task = weak.lock()) {
|
|
12
|
+
auto it = taskSubIds_.find(taskId);
|
|
13
|
+
if (it != taskSubIds_.end()) task->unsubscribe(it->second);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
void TaskObserverHub::track(const std::shared_ptr<SmbTask>& task) {
|
|
19
|
+
if (!task) return;
|
|
20
|
+
// Register as a normal subscriber; the task does not know this is "the hub".
|
|
21
|
+
std::string subId = task->subscribe([this](const SmbTaskState& s) { onTaskUpdate(s); });
|
|
22
|
+
std::unique_lock lock(mutex_);
|
|
23
|
+
tracked_[task->getId()] = task;
|
|
24
|
+
taskSubIds_[task->getId()] = subId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void TaskObserverHub::untrack(const std::string& taskId) {
|
|
28
|
+
std::shared_ptr<SmbTask> task;
|
|
29
|
+
std::string subId;
|
|
30
|
+
{
|
|
31
|
+
std::unique_lock lock(mutex_);
|
|
32
|
+
if (auto it = tracked_.find(taskId); it != tracked_.end()) {
|
|
33
|
+
task = it->second.lock();
|
|
34
|
+
tracked_.erase(it);
|
|
35
|
+
}
|
|
36
|
+
if (auto it = taskSubIds_.find(taskId); it != taskSubIds_.end()) {
|
|
37
|
+
subId = it->second;
|
|
38
|
+
taskSubIds_.erase(it);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (task && !subId.empty()) task->unsubscribe(subId);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void TaskObserverHub::onTaskUpdate(const SmbTaskState& snapshot) {
|
|
45
|
+
{
|
|
46
|
+
std::unique_lock lock(mutex_);
|
|
47
|
+
tasks_[snapshot.taskId] = snapshot;
|
|
48
|
+
if (snapshot.endedAt != 0) {
|
|
49
|
+
history_.push_back(snapshot);
|
|
50
|
+
while (history_.size() > kHistoryMax) history_.pop_front();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
dispatch(snapshot);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
void TaskObserverHub::dispatch(const SmbTaskState& snapshot) {
|
|
57
|
+
std::vector<SnapshotListener> all, transfer, active;
|
|
58
|
+
{
|
|
59
|
+
std::shared_lock lock(mutex_);
|
|
60
|
+
for (auto& [id, fn] : allSubs_) all.push_back(fn);
|
|
61
|
+
if (isTransfer(snapshot))
|
|
62
|
+
for (auto& [id, fn] : transferSubs_) transfer.push_back(fn);
|
|
63
|
+
if (isActive(snapshot))
|
|
64
|
+
for (auto& [id, fn] : activeSubs_) active.push_back(fn);
|
|
65
|
+
}
|
|
66
|
+
for (auto& fn : all)
|
|
67
|
+
if (fn) fn(snapshot);
|
|
68
|
+
for (auto& fn : transfer)
|
|
69
|
+
if (fn) fn(snapshot);
|
|
70
|
+
for (auto& fn : active)
|
|
71
|
+
if (fn) fn(snapshot);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
bool TaskObserverHub::isTransfer(const SmbTaskState& s) { return isTransferKind(s.kind); }
|
|
75
|
+
|
|
76
|
+
bool TaskObserverHub::isActive(const SmbTaskState& s) { return s.status == SmbTaskStatus::Running || s.status == SmbTaskStatus::Pending; }
|
|
77
|
+
|
|
78
|
+
std::string TaskObserverHub::subscribeAll(SnapshotListener listener) {
|
|
79
|
+
std::unique_lock lock(mutex_);
|
|
80
|
+
std::string id = "all_" + std::to_string(nextSubId_++);
|
|
81
|
+
allSubs_[id] = std::move(listener);
|
|
82
|
+
return id;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
std::string TaskObserverHub::subscribeTransfers(SnapshotListener listener) {
|
|
86
|
+
std::unique_lock lock(mutex_);
|
|
87
|
+
std::string id = "txf_" + std::to_string(nextSubId_++);
|
|
88
|
+
transferSubs_[id] = std::move(listener);
|
|
89
|
+
return id;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
std::string TaskObserverHub::subscribeActive(SnapshotListener listener) {
|
|
93
|
+
std::unique_lock lock(mutex_);
|
|
94
|
+
std::string id = "act_" + std::to_string(nextSubId_++);
|
|
95
|
+
activeSubs_[id] = std::move(listener);
|
|
96
|
+
return id;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void TaskObserverHub::unsubscribe(const std::string& id) {
|
|
100
|
+
std::unique_lock lock(mutex_);
|
|
101
|
+
allSubs_.erase(id);
|
|
102
|
+
transferSubs_.erase(id);
|
|
103
|
+
activeSubs_.erase(id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
std::vector<SmbTaskState> TaskObserverHub::getAll() const {
|
|
107
|
+
std::shared_lock lock(mutex_);
|
|
108
|
+
std::vector<SmbTaskState> out;
|
|
109
|
+
out.reserve(tasks_.size());
|
|
110
|
+
for (auto& [id, s] : tasks_) out.push_back(s);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
std::vector<SmbTaskState> TaskObserverHub::getTransfers() const {
|
|
115
|
+
std::shared_lock lock(mutex_);
|
|
116
|
+
std::vector<SmbTaskState> out;
|
|
117
|
+
for (auto& [id, s] : tasks_)
|
|
118
|
+
if (isTransfer(s)) out.push_back(s);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
std::vector<SmbTaskState> TaskObserverHub::getActive() const {
|
|
123
|
+
std::shared_lock lock(mutex_);
|
|
124
|
+
std::vector<SmbTaskState> out;
|
|
125
|
+
for (auto& [id, s] : tasks_)
|
|
126
|
+
if (isActive(s)) out.push_back(s);
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
std::vector<SmbTaskState> TaskObserverHub::getHistory(size_t limit, size_t offset) const {
|
|
131
|
+
std::shared_lock lock(mutex_);
|
|
132
|
+
std::vector<SmbTaskState> out;
|
|
133
|
+
if (offset >= history_.size()) return out;
|
|
134
|
+
// history_ is oldest-first; return newest-first for UI.
|
|
135
|
+
const size_t total = history_.size();
|
|
136
|
+
size_t startFromEnd = offset;
|
|
137
|
+
size_t count = 0;
|
|
138
|
+
for (size_t i = 0; i < total && count < limit; ++i) {
|
|
139
|
+
size_t idx = total - 1 - i; // newest first
|
|
140
|
+
if (i < startFromEnd) continue;
|
|
141
|
+
out.push_back(history_[idx]);
|
|
142
|
+
++count;
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
std::shared_ptr<SmbTaskState> TaskObserverHub::getTask(const std::string& id) const {
|
|
148
|
+
std::shared_lock lock(mutex_);
|
|
149
|
+
if (auto it = tasks_.find(id); it != tasks_.end()) return std::make_shared<SmbTaskState>(it->second);
|
|
150
|
+
return nullptr;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void TaskObserverHub::clearHistory(int64_t beforeTs) {
|
|
154
|
+
std::unique_lock lock(mutex_);
|
|
155
|
+
if (beforeTs <= 0) {
|
|
156
|
+
history_.clear();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
std::deque<SmbTaskState> kept;
|
|
160
|
+
for (auto& s : history_) {
|
|
161
|
+
if (s.endedAt >= beforeTs) kept.push_back(s);
|
|
162
|
+
}
|
|
163
|
+
history_.swap(kept);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
} // namespace react_native_smb
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <atomic>
|
|
4
|
+
#include <deque>
|
|
5
|
+
#include <map>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <shared_mutex>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <unordered_map>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
#include "SmbTypes.hpp"
|
|
13
|
+
|
|
14
|
+
namespace react_native_smb {
|
|
15
|
+
|
|
16
|
+
class SmbTask;
|
|
17
|
+
|
|
18
|
+
// Optional, per-instance global view of all tasks (transfer tray, debug panel).
|
|
19
|
+
// NOT a singleton. The hub SUBSCRIBES to tasks via SmbTask::subscribe(); tasks
|
|
20
|
+
// never reference the hub. Removing it leaves the library fully functional.
|
|
21
|
+
class TaskObserverHub {
|
|
22
|
+
public:
|
|
23
|
+
TaskObserverHub() = default;
|
|
24
|
+
~TaskObserverHub();
|
|
25
|
+
|
|
26
|
+
// Start/stop watching a task (called by HybridSMB on createTask / teardown).
|
|
27
|
+
void track(const std::shared_ptr<SmbTask>& task);
|
|
28
|
+
void untrack(const std::string& taskId);
|
|
29
|
+
|
|
30
|
+
// Downstream subscriptions.
|
|
31
|
+
std::string subscribeAll(SnapshotListener listener);
|
|
32
|
+
std::string subscribeTransfers(SnapshotListener listener); // download/upload/copy/duplicate
|
|
33
|
+
std::string subscribeActive(SnapshotListener listener); // Running / Pending
|
|
34
|
+
void unsubscribe(const std::string& id);
|
|
35
|
+
|
|
36
|
+
// Queries.
|
|
37
|
+
std::vector<SmbTaskState> getAll() const;
|
|
38
|
+
std::vector<SmbTaskState> getTransfers() const;
|
|
39
|
+
std::vector<SmbTaskState> getActive() const;
|
|
40
|
+
std::vector<SmbTaskState> getHistory(size_t limit = 100, size_t offset = 0) const;
|
|
41
|
+
std::shared_ptr<SmbTaskState> getTask(const std::string& id) const;
|
|
42
|
+
|
|
43
|
+
void clearHistory(int64_t beforeTs = 0);
|
|
44
|
+
|
|
45
|
+
private:
|
|
46
|
+
void onTaskUpdate(const SmbTaskState& snapshot); // the listener registered into each task
|
|
47
|
+
void dispatch(const SmbTaskState& snapshot);
|
|
48
|
+
static bool isTransfer(const SmbTaskState& s);
|
|
49
|
+
static bool isActive(const SmbTaskState& s);
|
|
50
|
+
|
|
51
|
+
std::unordered_map<std::string, SmbTaskState> tasks_; // latest snapshot per task
|
|
52
|
+
std::unordered_map<std::string, std::string> taskSubIds_; // taskId -> sub id in that task
|
|
53
|
+
std::unordered_map<std::string, std::weak_ptr<SmbTask>> tracked_;
|
|
54
|
+
std::deque<SmbTaskState> history_;
|
|
55
|
+
|
|
56
|
+
std::map<std::string, SnapshotListener> allSubs_;
|
|
57
|
+
std::map<std::string, SnapshotListener> transferSubs_;
|
|
58
|
+
std::map<std::string, SnapshotListener> activeSubs_;
|
|
59
|
+
|
|
60
|
+
mutable std::shared_mutex mutex_;
|
|
61
|
+
std::atomic<size_t> nextSubId_{1};
|
|
62
|
+
|
|
63
|
+
static constexpr size_t kHistoryMax = 2000;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
} // namespace react_native_smb
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#include "TaskSnapshotCodec.hpp"
|
|
2
|
+
|
|
3
|
+
#include "SmbEnums.hpp"
|
|
4
|
+
|
|
5
|
+
namespace react_native_smb {
|
|
6
|
+
|
|
7
|
+
std::unordered_map<std::string, std::string> TaskSnapshotCodec::encode(const SmbTaskState& s) {
|
|
8
|
+
return {
|
|
9
|
+
{Keys::exists, "1"},
|
|
10
|
+
{Keys::taskId, s.taskId},
|
|
11
|
+
{Keys::kind, std::to_string(static_cast<int>(s.kind))},
|
|
12
|
+
{Keys::operation, operationName(s.kind)},
|
|
13
|
+
{Keys::status, std::to_string(static_cast<int>(s.status))},
|
|
14
|
+
{Keys::determinate, isDeterminate(s.kind) ? "1" : "0"},
|
|
15
|
+
{Keys::progress, std::to_string(s.progress)},
|
|
16
|
+
{Keys::errorCode, std::to_string(s.errorCode)},
|
|
17
|
+
{Keys::errorMessage, s.errorMessage},
|
|
18
|
+
{Keys::sourcePath, s.sourcePath},
|
|
19
|
+
{Keys::destinationPath, s.destinationPath},
|
|
20
|
+
{Keys::bytesDone, std::to_string(s.bytesDone)},
|
|
21
|
+
{Keys::totalBytes, std::to_string(s.totalBytes)},
|
|
22
|
+
{Keys::bytesPerSecond, std::to_string(s.bytesPerSecond)},
|
|
23
|
+
{Keys::etaSeconds, std::to_string(s.etaSeconds)},
|
|
24
|
+
{Keys::startedAt, std::to_string(s.startedAt)},
|
|
25
|
+
{Keys::updatedAt, std::to_string(s.updatedAt)},
|
|
26
|
+
{Keys::endedAt, std::to_string(s.endedAt)},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
std::unordered_map<std::string, std::string> TaskSnapshotCodec::notFound() { return {{Keys::exists, "0"}}; }
|
|
31
|
+
|
|
32
|
+
} // namespace react_native_smb
|