sftp-push-sync 2.1.2 → 2.1.4

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.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * walkers.mjs
3
+ *
4
+ * @author Carsten Nichte, 2025 / https://carsten-nichte.de/
5
+ *
6
+ */
7
+ // src/helpers/walkers.mjs
8
+ import fsp from "fs/promises";
9
+ import path from "path";
10
+ import { toPosix } from "./directory.mjs";
11
+
12
+ /**
13
+ * Allgemeiner Local-Walker mit Filter + Progress
14
+ *
15
+ * filterFn(rel) → true/false
16
+ * options:
17
+ * - progress: ScanProgressController-ähnliches Objekt (updateChannel/done)
18
+ * - scanChunk: nach wievielen Dateien Progress aktualisieren
19
+ * - log: optionaler Fallback-Logger für non-TTY
20
+ */
21
+ export async function walkLocal(
22
+ root,
23
+ {
24
+ filterFn,
25
+ classifyFn, // optional: (rel) => { isText, isMedia }
26
+ progress = null,
27
+ scanChunk = 100,
28
+ log = null,
29
+ } = {}
30
+ ) {
31
+ const result = new Map();
32
+ let scanned = 0;
33
+
34
+ async function recurse(current) {
35
+ const entries = await fsp.readdir(current, { withFileTypes: true });
36
+
37
+ for (const entry of entries) {
38
+ const full = path.join(current, entry.name);
39
+
40
+ if (entry.isDirectory()) {
41
+ await recurse(full);
42
+ } else if (entry.isFile()) {
43
+ const rel = toPosix(path.relative(root, full));
44
+
45
+ if (filterFn && !filterFn(rel)) continue;
46
+
47
+ const stat = await fsp.stat(full);
48
+ const baseMeta = {
49
+ rel,
50
+ localPath: full,
51
+ size: stat.size,
52
+ mtimeMs: stat.mtimeMs,
53
+ };
54
+
55
+ const extra = classifyFn ? classifyFn(rel) : {};
56
+ result.set(rel, { ...baseMeta, ...extra });
57
+
58
+ scanned += 1;
59
+
60
+ if (
61
+ progress &&
62
+ (scanned === 1 || scanned % scanChunk === 0)
63
+ ) {
64
+ progress.updateChannel("local", {
65
+ label: "Scan local",
66
+ current: scanned,
67
+ total: 0,
68
+ lastRel: full,
69
+ });
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ await recurse(root);
76
+
77
+ if (progress) {
78
+ progress.updateChannel("local", {
79
+ label: "Scan local",
80
+ current: scanned,
81
+ total: result.size,
82
+ lastRel: null,
83
+ });
84
+ progress.done("local");
85
+ }
86
+
87
+ if (!process.stdout.isTTY && scanned > 0 && log) {
88
+ log(` Scan local: ${scanned} Files`);
89
+ }
90
+
91
+ return result;
92
+ }
93
+
94
+ /**
95
+ * Plain Local Walker – ohne Filter, ohne Klassifizierung
96
+ */
97
+ export async function walkLocalPlain(root) {
98
+ const result = new Map();
99
+
100
+ async function recurse(current) {
101
+ const entries = await fsp.readdir(current, { withFileTypes: true });
102
+ for (const entry of entries) {
103
+ const full = path.join(current, entry.name);
104
+ if (entry.isDirectory()) {
105
+ await recurse(full);
106
+ } else if (entry.isFile()) {
107
+ const rel = toPosix(path.relative(root, full));
108
+ result.set(rel, { rel, localPath: full });
109
+ }
110
+ }
111
+ }
112
+
113
+ await recurse(root);
114
+ return result;
115
+ }
116
+
117
+ /**
118
+ * Remote-Walker mit INCLUDE/EXCLUDE über filterFn
119
+ */
120
+ export async function walkRemote(
121
+ sftp,
122
+ remoteRoot,
123
+ {
124
+ filterFn,
125
+ progress = null,
126
+ scanChunk = 100,
127
+ log = null,
128
+ } = {}
129
+ ) {
130
+ const result = new Map();
131
+ let scanned = 0;
132
+
133
+ async function recurse(remoteDir, prefix) {
134
+ const items = await sftp.list(remoteDir);
135
+
136
+ for (const item of items) {
137
+ if (!item.name || item.name === "." || item.name === "..") continue;
138
+
139
+ const full = path.posix.join(remoteDir, item.name);
140
+ const rel = prefix ? `${prefix}/${item.name}` : item.name;
141
+
142
+ if (filterFn && !filterFn(rel)) continue;
143
+
144
+ if (item.type === "d") {
145
+ await recurse(full, rel);
146
+ } else {
147
+ result.set(rel, {
148
+ rel,
149
+ remotePath: full,
150
+ size: Number(item.size),
151
+ modifyTime: item.modifyTime ?? 0,
152
+ });
153
+
154
+ scanned += 1;
155
+
156
+ if (
157
+ progress &&
158
+ (scanned === 1 || scanned % scanChunk === 0)
159
+ ) {
160
+ progress.updateChannel("remote", {
161
+ label: "Scan remote",
162
+ current: scanned,
163
+ total: 0,
164
+ lastRel: full,
165
+ });
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ await recurse(remoteRoot, "");
172
+
173
+ if (progress) {
174
+ progress.updateChannel("remote", {
175
+ label: "Scan remote",
176
+ current: scanned,
177
+ total: result.size,
178
+ lastRel: null,
179
+ });
180
+ progress.done("remote");
181
+ }
182
+
183
+ if (!process.stdout.isTTY && scanned > 0 && log) {
184
+ log(` Scan remote: ${scanned} Files`);
185
+ }
186
+
187
+ return result;
188
+ }
189
+
190
+ /**
191
+ * Plain Remote Walker – ohne Filter
192
+ */
193
+ export async function walkRemotePlain(sftp, remoteRoot) {
194
+ const result = new Map();
195
+
196
+ async function recurse(remoteDir, prefix) {
197
+ const items = await sftp.list(remoteDir);
198
+
199
+ for (const item of items) {
200
+ if (!item.name || item.name === "." || item.name === "..") continue;
201
+
202
+ const full = path.posix.join(remoteDir, item.name);
203
+ const rel = prefix ? `${prefix}/${item.name}` : item.name;
204
+
205
+ if (item.type === "d") {
206
+ await recurse(full, rel);
207
+ } else {
208
+ result.set(rel, {
209
+ rel,
210
+ remotePath: full,
211
+ });
212
+ }
213
+ }
214
+ }
215
+
216
+ await recurse(remoteRoot, "");
217
+ return result;
218
+ }