@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
|
+
import { terminalStatus } from '../bridge/decode';
|
|
2
|
+
import { SmbError, SmbTaskError, TaskStatus } from '../types';
|
|
3
|
+
function errorForStatus(status, raw, taskId) {
|
|
4
|
+
const code = parseInt(raw.errorCode ?? '0', 10);
|
|
5
|
+
const message = raw.errorMessage || (status === TaskStatus.Cancelled ? 'Task cancelled' : 'Task failed');
|
|
6
|
+
return new SmbTaskError(message, Number.isFinite(code) ? code : SmbError.Unknown, taskId);
|
|
7
|
+
}
|
|
8
|
+
/** Subscribe once, resolve or reject when the task reaches a terminal status. */
|
|
9
|
+
export function taskResult(task) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
let settled = false;
|
|
12
|
+
const finish = (status, raw) => {
|
|
13
|
+
if (settled)
|
|
14
|
+
return;
|
|
15
|
+
settled = true;
|
|
16
|
+
if (status === TaskStatus.Success) {
|
|
17
|
+
try {
|
|
18
|
+
resolve(task.getResultValue());
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
reject(e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
reject(errorForStatus(status, raw, task.id));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const unsub = task.subscribe((snap) => {
|
|
29
|
+
if (snap.status === TaskStatus.Success ||
|
|
30
|
+
snap.status === TaskStatus.Error ||
|
|
31
|
+
snap.status === TaskStatus.Cancelled) {
|
|
32
|
+
finish(snap.status, {
|
|
33
|
+
errorCode: String(snap.errorCode),
|
|
34
|
+
errorMessage: snap.errorMessage,
|
|
35
|
+
});
|
|
36
|
+
unsub();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
const raw = task.getRaw();
|
|
41
|
+
const status = terminalStatus(raw);
|
|
42
|
+
if (status !== null) {
|
|
43
|
+
finish(status, raw);
|
|
44
|
+
unsub();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
unsub();
|
|
49
|
+
reject(e);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SmbTask } from '../core/SmbTask';
|
|
2
|
+
import { TaskStatus, type SmbTaskState } from '../types';
|
|
3
|
+
export interface SubscribeHandle<T = void> {
|
|
4
|
+
/** Start live updates. Returns unsubscribe. */
|
|
5
|
+
subscribe: (listener?: (snapshot: SmbTaskState) => void) => () => void;
|
|
6
|
+
cancel: () => void;
|
|
7
|
+
progress: number;
|
|
8
|
+
status: TaskStatus;
|
|
9
|
+
snapshot: SmbTaskState | null;
|
|
10
|
+
task: SmbTask<T> | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* React binding for {@link SmbTask.subscribe}. Subscribes automatically on mount
|
|
14
|
+
* and whenever `task` changes; unsubscribes on unmount / task change.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const task = smb.downloadFile(remote, local);
|
|
18
|
+
* const { progress, status, cancel } = useSubscribe(task);
|
|
19
|
+
*/
|
|
20
|
+
export declare function useSubscribe<T>(task: SmbTask<T> | null | undefined): SubscribeHandle<T>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { TaskStatus } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* React binding for {@link SmbTask.subscribe}. Subscribes automatically on mount
|
|
5
|
+
* and whenever `task` changes; unsubscribes on unmount / task change.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const task = smb.downloadFile(remote, local);
|
|
9
|
+
* const { progress, status, cancel } = useSubscribe(task);
|
|
10
|
+
*/
|
|
11
|
+
export function useSubscribe(task) {
|
|
12
|
+
const [snapshot, setSnapshot] = useState(() => task?.get() ?? null);
|
|
13
|
+
const subscribe = useCallback((listener) => {
|
|
14
|
+
if (!task)
|
|
15
|
+
return () => { };
|
|
16
|
+
return task.subscribe((snap) => {
|
|
17
|
+
setSnapshot(snap);
|
|
18
|
+
listener?.(snap);
|
|
19
|
+
});
|
|
20
|
+
}, [task]);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setSnapshot(task?.get() ?? null);
|
|
23
|
+
return subscribe();
|
|
24
|
+
}, [task, subscribe]);
|
|
25
|
+
const cancel = useCallback(() => {
|
|
26
|
+
task?.cancel();
|
|
27
|
+
}, [task]);
|
|
28
|
+
return {
|
|
29
|
+
subscribe,
|
|
30
|
+
cancel,
|
|
31
|
+
progress: snapshot?.progress ?? task?.progress ?? 0,
|
|
32
|
+
status: snapshot?.status ?? task?.status ?? TaskStatus.Pending,
|
|
33
|
+
snapshot,
|
|
34
|
+
task: task ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Smb } from '../core/SMB';
|
|
2
|
+
import type { SmbTaskState } from '../types';
|
|
3
|
+
export declare function useTransfers(smb: Smb | null): SmbTaskState[];
|
|
4
|
+
export declare function useTransferActions(smb: Smb | null): {
|
|
5
|
+
clearCompleted: () => void | undefined;
|
|
6
|
+
hide: (taskId: string) => void | undefined;
|
|
7
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
const EMPTY_TASKS = [];
|
|
3
|
+
function store(smb) {
|
|
4
|
+
return smb.transferStore();
|
|
5
|
+
}
|
|
6
|
+
export function useTransfers(smb) {
|
|
7
|
+
const [tasks, setTasks] = useState(EMPTY_TASKS);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!smb) {
|
|
10
|
+
setTasks(EMPTY_TASKS);
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const transferStore = store(smb);
|
|
14
|
+
const onStoreChange = () => {
|
|
15
|
+
setTasks(transferStore.getSnapshot());
|
|
16
|
+
};
|
|
17
|
+
const unsubscribe = transferStore.subscribe(onStoreChange);
|
|
18
|
+
onStoreChange();
|
|
19
|
+
return unsubscribe;
|
|
20
|
+
}, [smb]);
|
|
21
|
+
return smb ? tasks : EMPTY_TASKS;
|
|
22
|
+
}
|
|
23
|
+
export function useTransferActions(smb) {
|
|
24
|
+
const s = smb ? store(smb) : null;
|
|
25
|
+
return useMemo(() => ({
|
|
26
|
+
clearCompleted: () => s?.clearCompleted(),
|
|
27
|
+
hide: (taskId) => s?.hide(taskId),
|
|
28
|
+
}), [s]);
|
|
29
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './utils';
|
|
3
|
+
export { SMB, type Smb } from './core/SMB';
|
|
4
|
+
export { SmbTask } from './core/SmbTask';
|
|
5
|
+
export { useSubscribe, type SubscribeHandle } from './hooks/useSubscribe';
|
|
6
|
+
export { useTransfers, useTransferActions } from './hooks/useTransfers';
|
|
7
|
+
export { statusLabel, operationLabel, isTransferKind, isDeterminateKind, isTerminalStatus, } from './labels';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './utils';
|
|
3
|
+
export { SMB } from './core/SMB';
|
|
4
|
+
export { SmbTask } from './core/SmbTask';
|
|
5
|
+
export { useSubscribe } from './hooks/useSubscribe';
|
|
6
|
+
export { useTransfers, useTransferActions } from './hooks/useTransfers';
|
|
7
|
+
export { statusLabel, operationLabel, isTransferKind, isDeterminateKind, isTerminalStatus, } from './labels';
|
package/lib/labels.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { SmbOperatorKind, TaskStatus } from './types';
|
|
2
|
+
export declare function statusLabel(status: TaskStatus): string;
|
|
3
|
+
export declare function operationLabel(kind: SmbOperatorKind): string;
|
|
4
|
+
export declare function isTransferKind(kind: SmbOperatorKind): boolean;
|
|
5
|
+
export declare function isDeterminateKind(kind: SmbOperatorKind): boolean;
|
|
6
|
+
export declare function isTerminalStatus(status: TaskStatus): boolean;
|
package/lib/labels.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { SmbOperatorKind, TaskStatus } from './types';
|
|
2
|
+
export function statusLabel(status) {
|
|
3
|
+
switch (status) {
|
|
4
|
+
case TaskStatus.Idle:
|
|
5
|
+
return 'idle';
|
|
6
|
+
case TaskStatus.Pending:
|
|
7
|
+
return 'pending';
|
|
8
|
+
case TaskStatus.Running:
|
|
9
|
+
return 'running';
|
|
10
|
+
case TaskStatus.Success:
|
|
11
|
+
return 'success';
|
|
12
|
+
case TaskStatus.Error:
|
|
13
|
+
return 'error';
|
|
14
|
+
case TaskStatus.Cancelled:
|
|
15
|
+
return 'cancelled';
|
|
16
|
+
default:
|
|
17
|
+
return 'unknown';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function operationLabel(kind) {
|
|
21
|
+
switch (kind) {
|
|
22
|
+
case SmbOperatorKind.Initialize:
|
|
23
|
+
return 'Initialize';
|
|
24
|
+
case SmbOperatorKind.Connect:
|
|
25
|
+
return 'Connect';
|
|
26
|
+
case SmbOperatorKind.ConnectShare:
|
|
27
|
+
return 'Connect share';
|
|
28
|
+
case SmbOperatorKind.Disconnect:
|
|
29
|
+
return 'Disconnect';
|
|
30
|
+
case SmbOperatorKind.ListShares:
|
|
31
|
+
return 'List shares';
|
|
32
|
+
case SmbOperatorKind.ListDirectory:
|
|
33
|
+
return 'List directory';
|
|
34
|
+
case SmbOperatorKind.GetPathInfo:
|
|
35
|
+
return 'Get info';
|
|
36
|
+
case SmbOperatorKind.GetSecurityDescriptor:
|
|
37
|
+
return 'Get ACL';
|
|
38
|
+
case SmbOperatorKind.DownloadFile:
|
|
39
|
+
return 'Download';
|
|
40
|
+
case SmbOperatorKind.UploadFile:
|
|
41
|
+
return 'Upload';
|
|
42
|
+
case SmbOperatorKind.CreateDirectory:
|
|
43
|
+
return 'Create folder';
|
|
44
|
+
case SmbOperatorKind.DeleteItem:
|
|
45
|
+
return 'Delete';
|
|
46
|
+
case SmbOperatorKind.MoveItem:
|
|
47
|
+
return 'Move';
|
|
48
|
+
case SmbOperatorKind.RenameItem:
|
|
49
|
+
return 'Rename';
|
|
50
|
+
case SmbOperatorKind.CopyItem:
|
|
51
|
+
return 'Copy';
|
|
52
|
+
case SmbOperatorKind.DuplicateItem:
|
|
53
|
+
return 'Duplicate';
|
|
54
|
+
default:
|
|
55
|
+
return 'Unknown';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function isTransferKind(kind) {
|
|
59
|
+
return (kind === SmbOperatorKind.DownloadFile ||
|
|
60
|
+
kind === SmbOperatorKind.UploadFile ||
|
|
61
|
+
kind === SmbOperatorKind.CopyItem ||
|
|
62
|
+
kind === SmbOperatorKind.DuplicateItem);
|
|
63
|
+
}
|
|
64
|
+
export function isDeterminateKind(kind) {
|
|
65
|
+
return isTransferKind(kind);
|
|
66
|
+
}
|
|
67
|
+
export function isTerminalStatus(status) {
|
|
68
|
+
return (status === TaskStatus.Success ||
|
|
69
|
+
status === TaskStatus.Error ||
|
|
70
|
+
status === TaskStatus.Cancelled);
|
|
71
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export interface SmbCredentials {
|
|
2
|
+
readonly username: string;
|
|
3
|
+
readonly password: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SmbShareList {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly comment: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SmbConnectionInfo {
|
|
10
|
+
readonly url: string;
|
|
11
|
+
readonly server: string;
|
|
12
|
+
readonly share: string;
|
|
13
|
+
readonly isConnected: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare enum SmbError {
|
|
16
|
+
Unknown = 0,
|
|
17
|
+
Cancelled = 1,
|
|
18
|
+
InvalidArgument = 100,
|
|
19
|
+
NotFound = 101,
|
|
20
|
+
AlreadyExists = 102,
|
|
21
|
+
AccessDenied = 103,
|
|
22
|
+
NotDirectory = 104,
|
|
23
|
+
IsDirectory = 105,
|
|
24
|
+
DirectoryNotEmpty = 106,
|
|
25
|
+
NotConnected = 200,
|
|
26
|
+
ConnectionRefused = 201,
|
|
27
|
+
TimedOut = 202,
|
|
28
|
+
Busy = 300,
|
|
29
|
+
NoSpace = 301,
|
|
30
|
+
Io = 302
|
|
31
|
+
}
|
|
32
|
+
export declare class SmbTaskError extends Error {
|
|
33
|
+
readonly code: SmbError;
|
|
34
|
+
readonly taskId?: string;
|
|
35
|
+
constructor(message: string, code?: SmbError, taskId?: string);
|
|
36
|
+
}
|
|
37
|
+
export interface SmbFileInfo {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly path: string;
|
|
40
|
+
readonly size: number;
|
|
41
|
+
readonly isDirectory: boolean;
|
|
42
|
+
readonly modifiedAt: number;
|
|
43
|
+
readonly accessedAt: number;
|
|
44
|
+
readonly createdAt: number;
|
|
45
|
+
readonly changedAt: number;
|
|
46
|
+
readonly childCount?: number;
|
|
47
|
+
readonly children?: SmbFileInfo[];
|
|
48
|
+
}
|
|
49
|
+
export interface SmbAce {
|
|
50
|
+
readonly aceType: string;
|
|
51
|
+
readonly aceFlags: string[];
|
|
52
|
+
readonly mask: string[];
|
|
53
|
+
readonly sid: string;
|
|
54
|
+
}
|
|
55
|
+
export interface SmbAcl {
|
|
56
|
+
readonly revision: number;
|
|
57
|
+
readonly aceCount: number;
|
|
58
|
+
readonly aces: SmbAce[];
|
|
59
|
+
}
|
|
60
|
+
export interface SmbSecurityDescriptor {
|
|
61
|
+
readonly revision: number;
|
|
62
|
+
readonly control: string[];
|
|
63
|
+
readonly ownerSid: string;
|
|
64
|
+
readonly groupSid: string;
|
|
65
|
+
readonly dacl: SmbAcl;
|
|
66
|
+
}
|
|
67
|
+
export interface PathComponents {
|
|
68
|
+
readonly parentDirectory: string;
|
|
69
|
+
readonly baseFileName: string;
|
|
70
|
+
readonly extension: string;
|
|
71
|
+
readonly fullFileName: string;
|
|
72
|
+
}
|
|
73
|
+
export declare enum TaskStatus {
|
|
74
|
+
Idle = 0,
|
|
75
|
+
Pending = 1,
|
|
76
|
+
Running = 2,
|
|
77
|
+
Success = 3,
|
|
78
|
+
Error = 4,
|
|
79
|
+
Cancelled = 5
|
|
80
|
+
}
|
|
81
|
+
export declare enum SmbOperatorKind {
|
|
82
|
+
Initialize = 0,
|
|
83
|
+
Connect = 1,
|
|
84
|
+
ConnectShare = 2,
|
|
85
|
+
Disconnect = 3,
|
|
86
|
+
ListShares = 4,
|
|
87
|
+
ListDirectory = 5,
|
|
88
|
+
GetPathInfo = 6,
|
|
89
|
+
GetSecurityDescriptor = 7,
|
|
90
|
+
DownloadFile = 8,
|
|
91
|
+
UploadFile = 9,
|
|
92
|
+
CreateDirectory = 10,
|
|
93
|
+
DeleteItem = 11,
|
|
94
|
+
MoveItem = 12,
|
|
95
|
+
RenameItem = 13,
|
|
96
|
+
CopyItem = 14,
|
|
97
|
+
DuplicateItem = 15
|
|
98
|
+
}
|
|
99
|
+
export declare enum SlotState {
|
|
100
|
+
Idle = 0,
|
|
101
|
+
Assigned = 1,
|
|
102
|
+
Activating = 2
|
|
103
|
+
}
|
|
104
|
+
export interface SmbTaskState {
|
|
105
|
+
readonly taskId: string;
|
|
106
|
+
readonly kind: SmbOperatorKind;
|
|
107
|
+
readonly status: TaskStatus;
|
|
108
|
+
readonly determinate: boolean;
|
|
109
|
+
readonly progress: number;
|
|
110
|
+
readonly bytesDone: number;
|
|
111
|
+
readonly totalBytes: number;
|
|
112
|
+
readonly bytesPerSecond: number;
|
|
113
|
+
readonly etaSeconds: number;
|
|
114
|
+
readonly sourcePath: string;
|
|
115
|
+
readonly destinationPath: string;
|
|
116
|
+
readonly errorCode: number;
|
|
117
|
+
readonly errorMessage: string;
|
|
118
|
+
readonly startedAt: number;
|
|
119
|
+
readonly updatedAt: number;
|
|
120
|
+
readonly endedAt: number;
|
|
121
|
+
}
|
|
122
|
+
export interface PoolSlotInfo {
|
|
123
|
+
readonly index: number;
|
|
124
|
+
readonly interactiveOnly: boolean;
|
|
125
|
+
readonly state: SlotState;
|
|
126
|
+
readonly isConnected: boolean;
|
|
127
|
+
readonly shareName: string;
|
|
128
|
+
readonly taskId: string;
|
|
129
|
+
readonly kind: SmbOperatorKind;
|
|
130
|
+
}
|
|
131
|
+
export interface PoolInfo {
|
|
132
|
+
readonly poolSize: number;
|
|
133
|
+
readonly slots: PoolSlotInfo[];
|
|
134
|
+
}
|
|
135
|
+
export interface NativeSmbTask {
|
|
136
|
+
cancel(): void;
|
|
137
|
+
get(): Record<string, string>;
|
|
138
|
+
subscribe(listener: (snapshot: Record<string, string>) => void): string;
|
|
139
|
+
unsubscribe(subscriptionId: string): void;
|
|
140
|
+
getResultValue(): unknown;
|
|
141
|
+
}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export var SmbError;
|
|
2
|
+
(function (SmbError) {
|
|
3
|
+
SmbError[SmbError["Unknown"] = 0] = "Unknown";
|
|
4
|
+
SmbError[SmbError["Cancelled"] = 1] = "Cancelled";
|
|
5
|
+
SmbError[SmbError["InvalidArgument"] = 100] = "InvalidArgument";
|
|
6
|
+
SmbError[SmbError["NotFound"] = 101] = "NotFound";
|
|
7
|
+
SmbError[SmbError["AlreadyExists"] = 102] = "AlreadyExists";
|
|
8
|
+
SmbError[SmbError["AccessDenied"] = 103] = "AccessDenied";
|
|
9
|
+
SmbError[SmbError["NotDirectory"] = 104] = "NotDirectory";
|
|
10
|
+
SmbError[SmbError["IsDirectory"] = 105] = "IsDirectory";
|
|
11
|
+
SmbError[SmbError["DirectoryNotEmpty"] = 106] = "DirectoryNotEmpty";
|
|
12
|
+
SmbError[SmbError["NotConnected"] = 200] = "NotConnected";
|
|
13
|
+
SmbError[SmbError["ConnectionRefused"] = 201] = "ConnectionRefused";
|
|
14
|
+
SmbError[SmbError["TimedOut"] = 202] = "TimedOut";
|
|
15
|
+
SmbError[SmbError["Busy"] = 300] = "Busy";
|
|
16
|
+
SmbError[SmbError["NoSpace"] = 301] = "NoSpace";
|
|
17
|
+
SmbError[SmbError["Io"] = 302] = "Io";
|
|
18
|
+
})(SmbError || (SmbError = {}));
|
|
19
|
+
export class SmbTaskError extends Error {
|
|
20
|
+
constructor(message, code = SmbError.Unknown, taskId) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.taskId = taskId;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export var TaskStatus;
|
|
27
|
+
(function (TaskStatus) {
|
|
28
|
+
TaskStatus[TaskStatus["Idle"] = 0] = "Idle";
|
|
29
|
+
TaskStatus[TaskStatus["Pending"] = 1] = "Pending";
|
|
30
|
+
TaskStatus[TaskStatus["Running"] = 2] = "Running";
|
|
31
|
+
TaskStatus[TaskStatus["Success"] = 3] = "Success";
|
|
32
|
+
TaskStatus[TaskStatus["Error"] = 4] = "Error";
|
|
33
|
+
TaskStatus[TaskStatus["Cancelled"] = 5] = "Cancelled";
|
|
34
|
+
})(TaskStatus || (TaskStatus = {}));
|
|
35
|
+
export var SmbOperatorKind;
|
|
36
|
+
(function (SmbOperatorKind) {
|
|
37
|
+
SmbOperatorKind[SmbOperatorKind["Initialize"] = 0] = "Initialize";
|
|
38
|
+
SmbOperatorKind[SmbOperatorKind["Connect"] = 1] = "Connect";
|
|
39
|
+
SmbOperatorKind[SmbOperatorKind["ConnectShare"] = 2] = "ConnectShare";
|
|
40
|
+
SmbOperatorKind[SmbOperatorKind["Disconnect"] = 3] = "Disconnect";
|
|
41
|
+
SmbOperatorKind[SmbOperatorKind["ListShares"] = 4] = "ListShares";
|
|
42
|
+
SmbOperatorKind[SmbOperatorKind["ListDirectory"] = 5] = "ListDirectory";
|
|
43
|
+
SmbOperatorKind[SmbOperatorKind["GetPathInfo"] = 6] = "GetPathInfo";
|
|
44
|
+
SmbOperatorKind[SmbOperatorKind["GetSecurityDescriptor"] = 7] = "GetSecurityDescriptor";
|
|
45
|
+
SmbOperatorKind[SmbOperatorKind["DownloadFile"] = 8] = "DownloadFile";
|
|
46
|
+
SmbOperatorKind[SmbOperatorKind["UploadFile"] = 9] = "UploadFile";
|
|
47
|
+
SmbOperatorKind[SmbOperatorKind["CreateDirectory"] = 10] = "CreateDirectory";
|
|
48
|
+
SmbOperatorKind[SmbOperatorKind["DeleteItem"] = 11] = "DeleteItem";
|
|
49
|
+
SmbOperatorKind[SmbOperatorKind["MoveItem"] = 12] = "MoveItem";
|
|
50
|
+
SmbOperatorKind[SmbOperatorKind["RenameItem"] = 13] = "RenameItem";
|
|
51
|
+
SmbOperatorKind[SmbOperatorKind["CopyItem"] = 14] = "CopyItem";
|
|
52
|
+
SmbOperatorKind[SmbOperatorKind["DuplicateItem"] = 15] = "DuplicateItem";
|
|
53
|
+
})(SmbOperatorKind || (SmbOperatorKind = {}));
|
|
54
|
+
export var SlotState;
|
|
55
|
+
(function (SlotState) {
|
|
56
|
+
SlotState[SlotState["Idle"] = 0] = "Idle";
|
|
57
|
+
SlotState[SlotState["Assigned"] = 1] = "Assigned";
|
|
58
|
+
SlotState[SlotState["Activating"] = 2] = "Activating";
|
|
59
|
+
})(SlotState || (SlotState = {}));
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { PathComponents } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a path to use forward slashes and remove trailing slashes
|
|
4
|
+
* @param path - The path to normalize
|
|
5
|
+
* @returns The normalized path
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizePath(path: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Split a file path into its components
|
|
10
|
+
* @param path - The full path to split
|
|
11
|
+
* @returns PathComponents containing parent directory, base filename, extension, and full filename
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* splitPath('/folder/document.pdf')
|
|
15
|
+
* // Returns:
|
|
16
|
+
* // {
|
|
17
|
+
* // parentDirectory: '/folder',
|
|
18
|
+
* // baseFileName: 'document',
|
|
19
|
+
* // extension: '.pdf',
|
|
20
|
+
* // fullFileName: 'document.pdf'
|
|
21
|
+
* // }
|
|
22
|
+
*/
|
|
23
|
+
export declare function splitPath(path: string): PathComponents;
|
|
24
|
+
/**
|
|
25
|
+
* Join path components into a full path
|
|
26
|
+
* @param directory - The directory path
|
|
27
|
+
* @param filename - The filename to append
|
|
28
|
+
* @returns The combined path
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* joinPath('/folder', 'file.txt') // '/folder/file.txt'
|
|
32
|
+
* joinPath('/', 'file.txt') // '/file.txt'
|
|
33
|
+
*/
|
|
34
|
+
export declare function joinPath(directory: string, filename: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Generate a unique copy name for duplicating files/folders
|
|
37
|
+
* Creates names in the format: "name copy.ext", "name copy 2.ext", etc.
|
|
38
|
+
*
|
|
39
|
+
* @param baseName - The base filename without extension
|
|
40
|
+
* @param extension - The file extension (including the dot)
|
|
41
|
+
* @param existingNames - Array of existing filenames to check against
|
|
42
|
+
* @returns The new unique name
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* generateCopyName('document', '.pdf', ['document.pdf', 'document copy.pdf'])
|
|
46
|
+
* // Returns: 'document copy 2.pdf'
|
|
47
|
+
*/
|
|
48
|
+
export declare function generateCopyName(baseName: string, extension: string, existingNames: string[]): string;
|
|
49
|
+
/**
|
|
50
|
+
* Get the parent directory of a path
|
|
51
|
+
* @param path - The path to get parent from
|
|
52
|
+
* @returns The parent directory path
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* getParentPath('/folder/subfolder/file.txt') // '/folder/subfolder'
|
|
56
|
+
* getParentPath('/file.txt') // '/'
|
|
57
|
+
*/
|
|
58
|
+
export declare function getParentPath(path: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Get just the filename from a full path
|
|
61
|
+
* @param path - The full path
|
|
62
|
+
* @returns The filename
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* getFileName('/folder/document.pdf') // 'document.pdf'
|
|
66
|
+
*/
|
|
67
|
+
export declare function getFileName(path: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Format file size in human-readable format
|
|
70
|
+
* @param bytes - Size in bytes
|
|
71
|
+
* @returns Formatted string (e.g., "1.5 MB")
|
|
72
|
+
*/
|
|
73
|
+
export declare function formatFileSize(bytes: number): string;
|
|
74
|
+
/**
|
|
75
|
+
* Format a timestamp to a readable date string
|
|
76
|
+
* @param timestamp - Unix timestamp in seconds
|
|
77
|
+
* @returns Formatted date string
|
|
78
|
+
*/
|
|
79
|
+
export declare function formatDate(timestamp: number): string;
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a path to use forward slashes and remove trailing slashes
|
|
3
|
+
* @param path - The path to normalize
|
|
4
|
+
* @returns The normalized path
|
|
5
|
+
*/
|
|
6
|
+
export function normalizePath(path) {
|
|
7
|
+
const normalized = path.replace(/\\/g, '/');
|
|
8
|
+
return normalized === '/' ? normalized : normalized.replace(/\/+$/, '');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Split a file path into its components
|
|
12
|
+
* @param path - The full path to split
|
|
13
|
+
* @returns PathComponents containing parent directory, base filename, extension, and full filename
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* splitPath('/folder/document.pdf')
|
|
17
|
+
* // Returns:
|
|
18
|
+
* // {
|
|
19
|
+
* // parentDirectory: '/folder',
|
|
20
|
+
* // baseFileName: 'document',
|
|
21
|
+
* // extension: '.pdf',
|
|
22
|
+
* // fullFileName: 'document.pdf'
|
|
23
|
+
* // }
|
|
24
|
+
*/
|
|
25
|
+
export function splitPath(path) {
|
|
26
|
+
const normalizedPath = normalizePath(path);
|
|
27
|
+
const lastSlashIndex = normalizedPath.lastIndexOf('/');
|
|
28
|
+
const parentDirectory = lastSlashIndex >= 0
|
|
29
|
+
? normalizedPath.substring(0, lastSlashIndex) || '/'
|
|
30
|
+
: '/';
|
|
31
|
+
const fullFileName = lastSlashIndex >= 0
|
|
32
|
+
? normalizedPath.substring(lastSlashIndex + 1)
|
|
33
|
+
: normalizedPath;
|
|
34
|
+
const lastDotIndex = fullFileName.lastIndexOf('.');
|
|
35
|
+
const hasExtension = lastDotIndex > 0;
|
|
36
|
+
const baseFileName = hasExtension
|
|
37
|
+
? fullFileName.substring(0, lastDotIndex)
|
|
38
|
+
: fullFileName;
|
|
39
|
+
const extension = hasExtension
|
|
40
|
+
? fullFileName.substring(lastDotIndex)
|
|
41
|
+
: '';
|
|
42
|
+
return {
|
|
43
|
+
parentDirectory,
|
|
44
|
+
baseFileName,
|
|
45
|
+
extension,
|
|
46
|
+
fullFileName,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Join path components into a full path
|
|
51
|
+
* @param directory - The directory path
|
|
52
|
+
* @param filename - The filename to append
|
|
53
|
+
* @returns The combined path
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* joinPath('/folder', 'file.txt') // '/folder/file.txt'
|
|
57
|
+
* joinPath('/', 'file.txt') // '/file.txt'
|
|
58
|
+
*/
|
|
59
|
+
export function joinPath(directory, filename) {
|
|
60
|
+
if (directory === '/') {
|
|
61
|
+
return `/${filename}`;
|
|
62
|
+
}
|
|
63
|
+
return directory.endsWith('/')
|
|
64
|
+
? `${directory}${filename}`
|
|
65
|
+
: `${directory}/${filename}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generate a unique copy name for duplicating files/folders
|
|
69
|
+
* Creates names in the format: "name copy.ext", "name copy 2.ext", etc.
|
|
70
|
+
*
|
|
71
|
+
* @param baseName - The base filename without extension
|
|
72
|
+
* @param extension - The file extension (including the dot)
|
|
73
|
+
* @param existingNames - Array of existing filenames to check against
|
|
74
|
+
* @returns The new unique name
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* generateCopyName('document', '.pdf', ['document.pdf', 'document copy.pdf'])
|
|
78
|
+
* // Returns: 'document copy 2.pdf'
|
|
79
|
+
*/
|
|
80
|
+
export function generateCopyName(baseName, extension, existingNames) {
|
|
81
|
+
const copyPattern = /^(.+?)( copy)( \d+)?$/;
|
|
82
|
+
const match = baseName.match(copyPattern);
|
|
83
|
+
const rootName = baseName.replace(/ copy( \d+)?$/, '');
|
|
84
|
+
const copyNumbers = [];
|
|
85
|
+
existingNames.forEach((name) => {
|
|
86
|
+
const { baseFileName } = splitPath(name);
|
|
87
|
+
const _match = baseFileName.match(copyPattern);
|
|
88
|
+
if (!_match || rootName !== _match[1]) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!_match[3]) {
|
|
92
|
+
copyNumbers.push(1);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
copyNumbers.push(parseInt(_match[3].trim(), 10));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
let nextNumber = match ? parseInt(match[3]?.trim() || '1', 10) + 1 : 1;
|
|
99
|
+
while (copyNumbers.includes(nextNumber)) {
|
|
100
|
+
nextNumber++;
|
|
101
|
+
}
|
|
102
|
+
const copySuffix = nextNumber === 1 ? 'copy' : `copy ${nextNumber}`;
|
|
103
|
+
return `${rootName} ${copySuffix}${extension}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the parent directory of a path
|
|
107
|
+
* @param path - The path to get parent from
|
|
108
|
+
* @returns The parent directory path
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* getParentPath('/folder/subfolder/file.txt') // '/folder/subfolder'
|
|
112
|
+
* getParentPath('/file.txt') // '/'
|
|
113
|
+
*/
|
|
114
|
+
export function getParentPath(path) {
|
|
115
|
+
const { parentDirectory } = splitPath(path);
|
|
116
|
+
return parentDirectory;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get just the filename from a full path
|
|
120
|
+
* @param path - The full path
|
|
121
|
+
* @returns The filename
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* getFileName('/folder/document.pdf') // 'document.pdf'
|
|
125
|
+
*/
|
|
126
|
+
export function getFileName(path) {
|
|
127
|
+
const { fullFileName } = splitPath(path);
|
|
128
|
+
return fullFileName;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Format file size in human-readable format
|
|
132
|
+
* @param bytes - Size in bytes
|
|
133
|
+
* @returns Formatted string (e.g., "1.5 MB")
|
|
134
|
+
*/
|
|
135
|
+
export function formatFileSize(bytes) {
|
|
136
|
+
if (bytes === 0)
|
|
137
|
+
return '0 Bytes';
|
|
138
|
+
const k = 1024;
|
|
139
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
140
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
141
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Format a timestamp to a readable date string
|
|
145
|
+
* @param timestamp - Unix timestamp in seconds
|
|
146
|
+
* @returns Formatted date string
|
|
147
|
+
*/
|
|
148
|
+
export function formatDate(timestamp) {
|
|
149
|
+
if (!timestamp)
|
|
150
|
+
return 'N/A';
|
|
151
|
+
const date = new Date(timestamp * 1000);
|
|
152
|
+
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
153
|
+
}
|