@huyooo/file-explorer-core 0.2.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/dist/index.js ADDED
@@ -0,0 +1,1182 @@
1
+ // src/types.ts
2
+ var FileType = /* @__PURE__ */ ((FileType2) => {
3
+ FileType2["FOLDER"] = "folder";
4
+ FileType2["FILE"] = "file";
5
+ FileType2["IMAGE"] = "image";
6
+ FileType2["VIDEO"] = "video";
7
+ FileType2["MUSIC"] = "music";
8
+ FileType2["DOCUMENT"] = "document";
9
+ FileType2["CODE"] = "code";
10
+ FileType2["TEXT"] = "text";
11
+ FileType2["ARCHIVE"] = "archive";
12
+ FileType2["APPLICATION"] = "application";
13
+ FileType2["UNKNOWN"] = "unknown";
14
+ return FileType2;
15
+ })(FileType || {});
16
+
17
+ // src/utils/file-type.ts
18
+ import path from "path";
19
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
20
+ ".jpg",
21
+ ".jpeg",
22
+ ".png",
23
+ ".gif",
24
+ ".bmp",
25
+ ".webp",
26
+ ".svg",
27
+ ".ico",
28
+ ".tiff",
29
+ ".tif",
30
+ ".heic",
31
+ ".heif"
32
+ ]);
33
+ var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
34
+ ".mp4",
35
+ ".mov",
36
+ ".avi",
37
+ ".mkv",
38
+ ".wmv",
39
+ ".flv",
40
+ ".webm",
41
+ ".m4v",
42
+ ".3gp",
43
+ ".mpeg",
44
+ ".mpg"
45
+ ]);
46
+ var MUSIC_EXTENSIONS = /* @__PURE__ */ new Set([
47
+ ".mp3",
48
+ ".wav",
49
+ ".flac",
50
+ ".aac",
51
+ ".ogg",
52
+ ".wma",
53
+ ".m4a",
54
+ ".aiff",
55
+ ".alac"
56
+ ]);
57
+ var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([
58
+ ".pdf",
59
+ ".doc",
60
+ ".docx",
61
+ ".xls",
62
+ ".xlsx",
63
+ ".ppt",
64
+ ".pptx",
65
+ ".odt",
66
+ ".ods",
67
+ ".odp",
68
+ ".rtf",
69
+ ".pages",
70
+ ".numbers",
71
+ ".key"
72
+ ]);
73
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
74
+ ".js",
75
+ ".ts",
76
+ ".jsx",
77
+ ".tsx",
78
+ ".vue",
79
+ ".svelte",
80
+ ".py",
81
+ ".rb",
82
+ ".go",
83
+ ".rs",
84
+ ".java",
85
+ ".kt",
86
+ ".swift",
87
+ ".c",
88
+ ".cpp",
89
+ ".h",
90
+ ".hpp",
91
+ ".html",
92
+ ".css",
93
+ ".scss",
94
+ ".sass",
95
+ ".less",
96
+ ".json",
97
+ ".yaml",
98
+ ".yml",
99
+ ".toml",
100
+ ".xml",
101
+ ".sh",
102
+ ".bash",
103
+ ".zsh",
104
+ ".fish",
105
+ ".ps1",
106
+ ".sql",
107
+ ".graphql",
108
+ ".prisma"
109
+ ]);
110
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
111
+ ".txt",
112
+ ".md",
113
+ ".markdown",
114
+ ".log",
115
+ ".ini",
116
+ ".conf",
117
+ ".cfg",
118
+ ".env"
119
+ ]);
120
+ var ARCHIVE_EXTENSIONS = /* @__PURE__ */ new Set([
121
+ ".zip",
122
+ ".rar",
123
+ ".7z",
124
+ ".tar",
125
+ ".gz",
126
+ ".bz2",
127
+ ".xz",
128
+ ".dmg",
129
+ ".iso"
130
+ ]);
131
+ var APPLICATION_EXTENSIONS = /* @__PURE__ */ new Set([
132
+ ".app",
133
+ ".exe",
134
+ ".msi",
135
+ ".deb",
136
+ ".rpm",
137
+ ".pkg",
138
+ ".apk",
139
+ ".ipa"
140
+ ]);
141
+ function getFileType(filePath, stats) {
142
+ if (stats.isDirectory()) {
143
+ if (filePath.endsWith(".app")) {
144
+ return "application" /* APPLICATION */;
145
+ }
146
+ return "folder" /* FOLDER */;
147
+ }
148
+ const ext = path.extname(filePath).toLowerCase();
149
+ if (IMAGE_EXTENSIONS.has(ext)) return "image" /* IMAGE */;
150
+ if (VIDEO_EXTENSIONS.has(ext)) return "video" /* VIDEO */;
151
+ if (MUSIC_EXTENSIONS.has(ext)) return "music" /* MUSIC */;
152
+ if (DOCUMENT_EXTENSIONS.has(ext)) return "document" /* DOCUMENT */;
153
+ if (CODE_EXTENSIONS.has(ext)) return "code" /* CODE */;
154
+ if (TEXT_EXTENSIONS.has(ext)) return "text" /* TEXT */;
155
+ if (ARCHIVE_EXTENSIONS.has(ext)) return "archive" /* ARCHIVE */;
156
+ if (APPLICATION_EXTENSIONS.has(ext)) return "application" /* APPLICATION */;
157
+ return "file" /* FILE */;
158
+ }
159
+ function isMediaFile(type) {
160
+ return type === "image" /* IMAGE */ || type === "video" /* VIDEO */ || type === "music" /* MUSIC */;
161
+ }
162
+ function isPreviewable(type) {
163
+ return type === "image" /* IMAGE */ || type === "video" /* VIDEO */ || type === "music" /* MUSIC */ || type === "text" /* TEXT */ || type === "code" /* CODE */;
164
+ }
165
+
166
+ // src/utils/formatters.ts
167
+ function formatFileSize(bytes) {
168
+ if (bytes === 0) return "0 B";
169
+ const units = ["B", "KB", "MB", "GB", "TB"];
170
+ const k = 1024;
171
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
172
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${units[i]}`;
173
+ }
174
+ function formatDate(date) {
175
+ const now = /* @__PURE__ */ new Date();
176
+ const diff = now.getTime() - date.getTime();
177
+ const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
178
+ if (days === 0) {
179
+ return date.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
180
+ } else if (days === 1) {
181
+ return "\u6628\u5929";
182
+ } else if (days < 7) {
183
+ return `${days} \u5929\u524D`;
184
+ } else {
185
+ return date.toLocaleDateString("zh-CN", {
186
+ year: "numeric",
187
+ month: "2-digit",
188
+ day: "2-digit"
189
+ });
190
+ }
191
+ }
192
+ function formatDateTime(date) {
193
+ return date.toLocaleString("zh-CN", {
194
+ year: "numeric",
195
+ month: "2-digit",
196
+ day: "2-digit",
197
+ hour: "2-digit",
198
+ minute: "2-digit"
199
+ });
200
+ }
201
+
202
+ // src/operations/read.ts
203
+ import { promises as fs } from "fs";
204
+ import path2 from "path";
205
+ var defaultUrlEncoder = (filePath) => `file://${encodeURIComponent(filePath)}`;
206
+ async function readDirectory(dirPath, options = {}) {
207
+ const {
208
+ urlEncoder = defaultUrlEncoder,
209
+ includeHidden = false,
210
+ getThumbnailUrl
211
+ } = options;
212
+ try {
213
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
214
+ const items = [];
215
+ for (const entry of entries) {
216
+ if (!includeHidden && entry.name.startsWith(".")) {
217
+ continue;
218
+ }
219
+ const fullPath = path2.join(dirPath, entry.name);
220
+ try {
221
+ let stats;
222
+ try {
223
+ stats = await fs.lstat(fullPath);
224
+ } catch {
225
+ try {
226
+ stats = await fs.stat(fullPath);
227
+ } catch (statError) {
228
+ const err = statError;
229
+ if (err.code !== "ENOENT") {
230
+ console.warn(`Cannot access file ${fullPath}:`, err.message);
231
+ }
232
+ continue;
233
+ }
234
+ }
235
+ const fileType = getFileType(fullPath, stats);
236
+ const fileUrl = urlEncoder(fullPath);
237
+ const item = {
238
+ id: fullPath,
239
+ name: entry.name,
240
+ type: fileType,
241
+ dateModified: formatDate(stats.mtime),
242
+ url: fileUrl
243
+ };
244
+ if (stats.isDirectory()) {
245
+ item.children = [];
246
+ } else {
247
+ item.size = formatFileSize(stats.size);
248
+ if (getThumbnailUrl && (fileType === "image" /* IMAGE */ || fileType === "video" /* VIDEO */)) {
249
+ const thumbUrl = await getThumbnailUrl(fullPath);
250
+ if (thumbUrl) {
251
+ item.thumbnailUrl = thumbUrl;
252
+ }
253
+ }
254
+ }
255
+ items.push(item);
256
+ } catch (itemError) {
257
+ const err = itemError;
258
+ if (err.code !== "ENOENT") {
259
+ console.warn(`Error processing file ${fullPath}:`, err.message);
260
+ }
261
+ continue;
262
+ }
263
+ }
264
+ items.sort((a, b) => {
265
+ if (a.type === "folder" /* FOLDER */ && b.type !== "folder" /* FOLDER */) return -1;
266
+ if (a.type !== "folder" /* FOLDER */ && b.type === "folder" /* FOLDER */) return 1;
267
+ return a.name.localeCompare(b.name, "zh-CN");
268
+ });
269
+ return items;
270
+ } catch (error) {
271
+ console.error(`Error reading directory ${dirPath}:`, error);
272
+ return [];
273
+ }
274
+ }
275
+ async function readFileContent(filePath) {
276
+ return await fs.readFile(filePath, "utf-8");
277
+ }
278
+ async function readImageAsBase64(imagePath) {
279
+ try {
280
+ let actualPath = imagePath;
281
+ if (imagePath.startsWith("app://file")) {
282
+ actualPath = decodeURIComponent(imagePath.replace("app://file", ""));
283
+ } else if (imagePath.startsWith("file://")) {
284
+ actualPath = decodeURIComponent(imagePath.replace("file://", ""));
285
+ }
286
+ const stats = await fs.stat(actualPath);
287
+ if (!stats.isFile()) {
288
+ return { success: false, error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${actualPath}` };
289
+ }
290
+ const buffer = await fs.readFile(actualPath);
291
+ const base64 = buffer.toString("base64");
292
+ const ext = path2.extname(actualPath).toLowerCase().slice(1);
293
+ const mimeTypes = {
294
+ jpg: "image/jpeg",
295
+ jpeg: "image/jpeg",
296
+ png: "image/png",
297
+ gif: "image/gif",
298
+ webp: "image/webp",
299
+ bmp: "image/bmp",
300
+ svg: "image/svg+xml"
301
+ };
302
+ const mimeType = mimeTypes[ext] || "image/jpeg";
303
+ return { success: true, base64, mimeType };
304
+ } catch (error) {
305
+ return { success: false, error: String(error) };
306
+ }
307
+ }
308
+
309
+ // src/operations/write.ts
310
+ import { promises as fs2 } from "fs";
311
+ import path3 from "path";
312
+ async function writeFileContent(filePath, content) {
313
+ try {
314
+ await fs2.writeFile(filePath, content, "utf-8");
315
+ return { success: true };
316
+ } catch (error) {
317
+ return { success: false, error: String(error) };
318
+ }
319
+ }
320
+ async function createFolder(folderPath) {
321
+ try {
322
+ try {
323
+ await fs2.access(folderPath);
324
+ } catch {
325
+ await fs2.mkdir(folderPath, { recursive: true });
326
+ return { success: true, data: { finalPath: folderPath } };
327
+ }
328
+ const dir = path3.dirname(folderPath);
329
+ const baseName = path3.basename(folderPath);
330
+ let counter = 2;
331
+ let finalPath = path3.join(dir, `${baseName} ${counter}`);
332
+ while (true) {
333
+ try {
334
+ await fs2.access(finalPath);
335
+ counter++;
336
+ finalPath = path3.join(dir, `${baseName} ${counter}`);
337
+ } catch {
338
+ break;
339
+ }
340
+ }
341
+ await fs2.mkdir(finalPath, { recursive: true });
342
+ return { success: true, data: { finalPath } };
343
+ } catch (error) {
344
+ return { success: false, error: String(error) };
345
+ }
346
+ }
347
+ async function createFile(filePath, content = "") {
348
+ try {
349
+ const dir = path3.dirname(filePath);
350
+ await fs2.mkdir(dir, { recursive: true });
351
+ try {
352
+ await fs2.access(filePath);
353
+ } catch {
354
+ await fs2.writeFile(filePath, content, "utf-8");
355
+ return { success: true, data: { finalPath: filePath } };
356
+ }
357
+ const dirname = path3.dirname(filePath);
358
+ const ext = path3.extname(filePath);
359
+ const basename = path3.basename(filePath, ext);
360
+ let counter = 2;
361
+ let finalPath = path3.join(dirname, `${basename} ${counter}${ext}`);
362
+ while (true) {
363
+ try {
364
+ await fs2.access(finalPath);
365
+ counter++;
366
+ finalPath = path3.join(dirname, `${basename} ${counter}${ext}`);
367
+ } catch {
368
+ break;
369
+ }
370
+ }
371
+ await fs2.writeFile(finalPath, content, "utf-8");
372
+ return { success: true, data: { finalPath } };
373
+ } catch (error) {
374
+ return { success: false, error: String(error) };
375
+ }
376
+ }
377
+
378
+ // src/operations/delete.ts
379
+ import { promises as fs3 } from "fs";
380
+ async function deleteFiles(paths, options = {}) {
381
+ const { adapter, useTrash = true, onDeleted } = options;
382
+ try {
383
+ for (const filePath of paths) {
384
+ if (useTrash && adapter?.trashItem) {
385
+ await adapter.trashItem(filePath);
386
+ } else {
387
+ const stats = await fs3.stat(filePath);
388
+ if (stats.isDirectory()) {
389
+ await fs3.rm(filePath, { recursive: true, force: true });
390
+ } else {
391
+ await fs3.unlink(filePath);
392
+ }
393
+ }
394
+ onDeleted?.(filePath);
395
+ }
396
+ return {
397
+ success: true,
398
+ message: useTrash ? "\u6587\u4EF6\u5DF2\u79FB\u52A8\u5230\u56DE\u6536\u7AD9" : "\u6587\u4EF6\u5DF2\u5220\u9664"
399
+ };
400
+ } catch (error) {
401
+ return { success: false, error: String(error) };
402
+ }
403
+ }
404
+
405
+ // src/operations/rename.ts
406
+ import { promises as fs4 } from "fs";
407
+ async function renameFile(oldPath, newPath, options = {}) {
408
+ const { onRenamed } = options;
409
+ try {
410
+ await fs4.rename(oldPath, newPath);
411
+ onRenamed?.(oldPath, newPath);
412
+ return { success: true };
413
+ } catch (error) {
414
+ return { success: false, error: String(error) };
415
+ }
416
+ }
417
+
418
+ // src/operations/copy.ts
419
+ import { promises as fs5 } from "fs";
420
+ import path4 from "path";
421
+ async function copyFiles(sourcePaths, targetDir) {
422
+ try {
423
+ const copiedPaths = [];
424
+ for (const sourcePath of sourcePaths) {
425
+ const fileName = path4.basename(sourcePath);
426
+ let destPath = path4.join(targetDir, fileName);
427
+ let counter = 1;
428
+ while (true) {
429
+ try {
430
+ await fs5.access(destPath);
431
+ const ext = path4.extname(fileName);
432
+ const baseName = path4.basename(fileName, ext);
433
+ destPath = path4.join(targetDir, `${baseName} ${++counter}${ext}`);
434
+ } catch {
435
+ break;
436
+ }
437
+ }
438
+ const stats = await fs5.stat(sourcePath);
439
+ if (stats.isDirectory()) {
440
+ await copyDirectory(sourcePath, destPath);
441
+ } else {
442
+ await fs5.copyFile(sourcePath, destPath);
443
+ }
444
+ copiedPaths.push(destPath);
445
+ }
446
+ return { success: true, data: { copiedPaths } };
447
+ } catch (error) {
448
+ return { success: false, error: String(error) };
449
+ }
450
+ }
451
+ async function moveFiles(sourcePaths, targetDir) {
452
+ try {
453
+ const movedPaths = [];
454
+ for (const sourcePath of sourcePaths) {
455
+ const fileName = path4.basename(sourcePath);
456
+ let destPath = path4.join(targetDir, fileName);
457
+ let counter = 1;
458
+ while (true) {
459
+ try {
460
+ await fs5.access(destPath);
461
+ const ext = path4.extname(fileName);
462
+ const baseName = path4.basename(fileName, ext);
463
+ destPath = path4.join(targetDir, `${baseName} ${++counter}${ext}`);
464
+ } catch {
465
+ break;
466
+ }
467
+ }
468
+ try {
469
+ await fs5.rename(sourcePath, destPath);
470
+ } catch {
471
+ const stats = await fs5.stat(sourcePath);
472
+ if (stats.isDirectory()) {
473
+ await copyDirectory(sourcePath, destPath);
474
+ } else {
475
+ await fs5.copyFile(sourcePath, destPath);
476
+ }
477
+ await fs5.rm(sourcePath, { recursive: true, force: true });
478
+ }
479
+ movedPaths.push(destPath);
480
+ }
481
+ return { success: true, data: { movedPaths } };
482
+ } catch (error) {
483
+ return { success: false, error: String(error) };
484
+ }
485
+ }
486
+ async function copyDirectory(source, dest) {
487
+ await fs5.mkdir(dest, { recursive: true });
488
+ const entries = await fs5.readdir(source, { withFileTypes: true });
489
+ for (const entry of entries) {
490
+ const sourcePath = path4.join(source, entry.name);
491
+ const destPath = path4.join(dest, entry.name);
492
+ if (entry.isDirectory()) {
493
+ await copyDirectory(sourcePath, destPath);
494
+ } else {
495
+ await fs5.copyFile(sourcePath, destPath);
496
+ }
497
+ }
498
+ }
499
+
500
+ // src/operations/info.ts
501
+ import { promises as fs6 } from "fs";
502
+ import path5 from "path";
503
+ async function getFileInfo(filePath) {
504
+ try {
505
+ const stats = await fs6.stat(filePath);
506
+ const name = path5.basename(filePath);
507
+ const ext = path5.extname(name);
508
+ return {
509
+ success: true,
510
+ data: {
511
+ path: filePath,
512
+ name,
513
+ size: stats.size,
514
+ isFile: stats.isFile(),
515
+ isDirectory: stats.isDirectory(),
516
+ createdAt: stats.birthtime,
517
+ updatedAt: stats.mtime,
518
+ extension: ext || void 0
519
+ }
520
+ };
521
+ } catch (error) {
522
+ return { success: false, error: String(error) };
523
+ }
524
+ }
525
+ async function exists(filePath) {
526
+ try {
527
+ await fs6.access(filePath);
528
+ return true;
529
+ } catch {
530
+ return false;
531
+ }
532
+ }
533
+ async function isDirectory(filePath) {
534
+ try {
535
+ const stats = await fs6.stat(filePath);
536
+ return stats.isDirectory();
537
+ } catch {
538
+ return false;
539
+ }
540
+ }
541
+
542
+ // src/system-paths.ts
543
+ import path6 from "path";
544
+ import os from "os";
545
+ var platform = process.platform;
546
+ function getSystemPath(pathId) {
547
+ const homeDir = os.homedir();
548
+ switch (pathId) {
549
+ case "desktop":
550
+ return path6.join(homeDir, "Desktop");
551
+ case "documents":
552
+ return path6.join(homeDir, "Documents");
553
+ case "downloads":
554
+ return path6.join(homeDir, "Downloads");
555
+ case "pictures":
556
+ return path6.join(homeDir, "Pictures");
557
+ case "music":
558
+ return path6.join(homeDir, "Music");
559
+ case "videos":
560
+ return platform === "darwin" ? path6.join(homeDir, "Movies") : path6.join(homeDir, "Videos");
561
+ case "applications":
562
+ return platform === "darwin" ? "/Applications" : platform === "win32" ? process.env.ProgramFiles || "C:\\Program Files" : "/usr/share/applications";
563
+ case "home":
564
+ return homeDir;
565
+ case "root":
566
+ return platform === "darwin" ? "/" : platform === "win32" ? "C:\\" : "/";
567
+ default:
568
+ return null;
569
+ }
570
+ }
571
+ function getAllSystemPaths() {
572
+ const pathIds = [
573
+ "desktop",
574
+ "documents",
575
+ "downloads",
576
+ "pictures",
577
+ "music",
578
+ "videos",
579
+ "applications",
580
+ "home",
581
+ "root"
582
+ ];
583
+ const result = {};
584
+ for (const id of pathIds) {
585
+ result[id] = getSystemPath(id);
586
+ }
587
+ return result;
588
+ }
589
+ function getHomeDirectory() {
590
+ return os.homedir();
591
+ }
592
+ function getPlatform() {
593
+ return process.platform;
594
+ }
595
+
596
+ // src/search.ts
597
+ import { fdir } from "fdir";
598
+ import path7 from "path";
599
+ import { promises as fs7 } from "fs";
600
+ function patternToRegex(pattern) {
601
+ return new RegExp(pattern.replace(/\*/g, ".*"), "i");
602
+ }
603
+ async function searchFiles(searchPath, pattern, maxDepth) {
604
+ const builder = new fdir().withFullPaths().withDirs();
605
+ if (maxDepth && maxDepth > 0) {
606
+ builder.withMaxDepth(maxDepth);
607
+ }
608
+ const api = builder.crawl(searchPath);
609
+ const files = await api.withPromise();
610
+ if (pattern) {
611
+ const regex = patternToRegex(pattern);
612
+ return files.filter((file) => regex.test(path7.basename(file)));
613
+ }
614
+ return files;
615
+ }
616
+ async function searchFilesStream(searchPath, pattern, onResults, maxResults = 100) {
617
+ const regex = patternToRegex(pattern);
618
+ const results = [];
619
+ let stopped = false;
620
+ async function searchDir(dirPath) {
621
+ if (stopped) return;
622
+ try {
623
+ const entries = await fs7.readdir(dirPath, { withFileTypes: true });
624
+ const matched = [];
625
+ const subdirs = [];
626
+ for (const entry of entries) {
627
+ if (stopped) return;
628
+ const fullPath = path7.join(dirPath, entry.name);
629
+ if (regex.test(entry.name)) {
630
+ matched.push(fullPath);
631
+ results.push(fullPath);
632
+ if (results.length >= maxResults) {
633
+ stopped = true;
634
+ onResults(matched, true);
635
+ return;
636
+ }
637
+ }
638
+ if (entry.isDirectory()) {
639
+ subdirs.push(fullPath);
640
+ }
641
+ }
642
+ if (matched.length > 0) {
643
+ onResults(matched, false);
644
+ }
645
+ for (const subdir of subdirs) {
646
+ await searchDir(subdir);
647
+ }
648
+ } catch {
649
+ }
650
+ }
651
+ await searchDir(searchPath);
652
+ if (!stopped) {
653
+ onResults([], true);
654
+ }
655
+ }
656
+ function searchFilesSync(searchPath, pattern, maxDepth) {
657
+ const builder = new fdir().withFullPaths().withDirs();
658
+ if (maxDepth && maxDepth > 0) {
659
+ builder.withMaxDepth(maxDepth);
660
+ }
661
+ const api = builder.crawl(searchPath);
662
+ const files = api.sync();
663
+ if (pattern) {
664
+ const regex = new RegExp(pattern.replace(/\*/g, ".*"), "i");
665
+ return files.filter((file) => regex.test(path7.basename(file)));
666
+ }
667
+ return files;
668
+ }
669
+
670
+ // src/hash.ts
671
+ import { createHash } from "crypto";
672
+ import { stat } from "fs/promises";
673
+ async function getFileHash(filePath) {
674
+ try {
675
+ const stats = await stat(filePath);
676
+ const hashInput = `${filePath}:${stats.size}:${stats.mtime.getTime()}`;
677
+ return createHash("md5").update(hashInput).digest("hex");
678
+ } catch (error) {
679
+ console.error(`Error computing hash for ${filePath}:`, error);
680
+ return createHash("md5").update(filePath).digest("hex");
681
+ }
682
+ }
683
+ async function getFileHashes(filePaths) {
684
+ const hashMap = /* @__PURE__ */ new Map();
685
+ await Promise.allSettled(
686
+ filePaths.map(async (filePath) => {
687
+ const hash = await getFileHash(filePath);
688
+ hashMap.set(filePath, hash);
689
+ })
690
+ );
691
+ return hashMap;
692
+ }
693
+
694
+ // src/clipboard.ts
695
+ import { promises as fs8 } from "fs";
696
+ import path8 from "path";
697
+ async function copyFilesToClipboard(filePaths, clipboard) {
698
+ try {
699
+ const cleanPaths = [];
700
+ for (const p of filePaths) {
701
+ try {
702
+ await fs8.access(p);
703
+ cleanPaths.push(p);
704
+ } catch {
705
+ }
706
+ }
707
+ if (cleanPaths.length === 0) {
708
+ return { success: false, error: "\u6CA1\u6709\u627E\u5230\u6709\u6548\u7684\u6587\u4EF6" };
709
+ }
710
+ clipboard.writeFiles(cleanPaths);
711
+ return { success: true };
712
+ } catch (error) {
713
+ return { success: false, error: String(error) };
714
+ }
715
+ }
716
+ function getClipboardFiles(clipboard) {
717
+ try {
718
+ const files = clipboard.readFiles();
719
+ if (files && Array.isArray(files) && files.length > 0) {
720
+ return { success: true, data: { files } };
721
+ }
722
+ return { success: false, error: "\u526A\u8D34\u677F\u4E2D\u6CA1\u6709\u6587\u4EF6" };
723
+ } catch (error) {
724
+ return { success: false, error: String(error) };
725
+ }
726
+ }
727
+ async function pasteFiles(targetDir, sourcePaths) {
728
+ try {
729
+ if (!targetDir || typeof targetDir !== "string") {
730
+ return { success: false, error: "\u76EE\u6807\u76EE\u5F55\u8DEF\u5F84\u65E0\u6548" };
731
+ }
732
+ if (!sourcePaths || !Array.isArray(sourcePaths) || sourcePaths.length === 0) {
733
+ return { success: false, error: "\u6E90\u6587\u4EF6\u8DEF\u5F84\u5217\u8868\u65E0\u6548" };
734
+ }
735
+ const pastedPaths = [];
736
+ for (const sourcePath of sourcePaths) {
737
+ const fileName = path8.basename(sourcePath);
738
+ let destPath = path8.join(targetDir, fileName);
739
+ let counter = 1;
740
+ while (true) {
741
+ try {
742
+ await fs8.access(destPath);
743
+ const ext = path8.extname(fileName);
744
+ const baseName = path8.basename(fileName, ext);
745
+ destPath = path8.join(targetDir, `${baseName} ${++counter}${ext}`);
746
+ } catch {
747
+ break;
748
+ }
749
+ }
750
+ const stats = await fs8.stat(sourcePath);
751
+ if (stats.isDirectory()) {
752
+ await copyDirectory2(sourcePath, destPath);
753
+ } else {
754
+ await fs8.copyFile(sourcePath, destPath);
755
+ }
756
+ pastedPaths.push(destPath);
757
+ }
758
+ return { success: true, data: { pastedPaths } };
759
+ } catch (error) {
760
+ return { success: false, error: String(error) };
761
+ }
762
+ }
763
+ async function copyDirectory2(source, dest) {
764
+ await fs8.mkdir(dest, { recursive: true });
765
+ const entries = await fs8.readdir(source, { withFileTypes: true });
766
+ for (const entry of entries) {
767
+ const sourcePath = path8.join(source, entry.name);
768
+ const destPath = path8.join(dest, entry.name);
769
+ if (entry.isDirectory()) {
770
+ await copyDirectory2(sourcePath, destPath);
771
+ } else {
772
+ await fs8.copyFile(sourcePath, destPath);
773
+ }
774
+ }
775
+ }
776
+
777
+ // src/application-icon.ts
778
+ import { promises as fs9 } from "fs";
779
+ import path9 from "path";
780
+ import os2 from "os";
781
+ import { exec } from "child_process";
782
+ import { promisify } from "util";
783
+ var execAsync = promisify(exec);
784
+ async function findAppIconPath(appPath) {
785
+ const resourcesPath = path9.join(appPath, "Contents", "Resources");
786
+ try {
787
+ const commonIconNames = [
788
+ "AppIcon.icns",
789
+ "app.icns",
790
+ "application.icns",
791
+ "icon.icns"
792
+ ];
793
+ const infoPlistPath = path9.join(appPath, "Contents", "Info.plist");
794
+ try {
795
+ const infoPlistContent = await fs9.readFile(infoPlistPath, "utf-8");
796
+ const iconFileMatch = infoPlistContent.match(/<key>CFBundleIconFile<\/key>\s*<string>([^<]+)<\/string>/);
797
+ if (iconFileMatch && iconFileMatch[1]) {
798
+ let iconFileName = iconFileMatch[1].trim();
799
+ if (!iconFileName.endsWith(".icns")) {
800
+ iconFileName += ".icns";
801
+ }
802
+ const iconPath = path9.join(resourcesPath, iconFileName);
803
+ try {
804
+ await fs9.access(iconPath);
805
+ return iconPath;
806
+ } catch {
807
+ }
808
+ }
809
+ } catch {
810
+ }
811
+ for (const iconName of commonIconNames) {
812
+ const iconPath = path9.join(resourcesPath, iconName);
813
+ try {
814
+ await fs9.access(iconPath);
815
+ return iconPath;
816
+ } catch {
817
+ continue;
818
+ }
819
+ }
820
+ try {
821
+ const entries = await fs9.readdir(resourcesPath);
822
+ const icnsFile = entries.find((entry) => entry.toLowerCase().endsWith(".icns"));
823
+ if (icnsFile) {
824
+ return path9.join(resourcesPath, icnsFile);
825
+ }
826
+ } catch {
827
+ }
828
+ return null;
829
+ } catch {
830
+ return null;
831
+ }
832
+ }
833
+ async function getApplicationIcon(appPath) {
834
+ if (process.platform !== "darwin") {
835
+ return null;
836
+ }
837
+ try {
838
+ const stats = await fs9.stat(appPath);
839
+ if (!stats.isDirectory() || !appPath.endsWith(".app")) {
840
+ return null;
841
+ }
842
+ } catch {
843
+ return null;
844
+ }
845
+ const iconPath = await findAppIconPath(appPath);
846
+ if (!iconPath) {
847
+ return null;
848
+ }
849
+ try {
850
+ const tempPngPath = path9.join(os2.tmpdir(), `app-icon-${Date.now()}.png`);
851
+ await execAsync(
852
+ `sips -s format png "${iconPath}" --out "${tempPngPath}" --resampleHeightWidthMax 128`
853
+ );
854
+ const pngBuffer = await fs9.readFile(tempPngPath);
855
+ try {
856
+ await fs9.unlink(tempPngPath);
857
+ } catch {
858
+ }
859
+ const base64 = pngBuffer.toString("base64");
860
+ return `data:image/png;base64,${base64}`;
861
+ } catch {
862
+ return null;
863
+ }
864
+ }
865
+
866
+ // src/thumbnail/service.ts
867
+ import { promises as fs10 } from "fs";
868
+ import path10 from "path";
869
+ var IMAGE_EXTENSIONS2 = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
870
+ var VIDEO_EXTENSIONS2 = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".wmv"];
871
+ var ThumbnailService = class {
872
+ database;
873
+ imageProcessor;
874
+ videoProcessor;
875
+ urlEncoder;
876
+ getApplicationIcon;
877
+ constructor(options) {
878
+ this.database = options.database;
879
+ this.imageProcessor = options.imageProcessor || null;
880
+ this.videoProcessor = options.videoProcessor || null;
881
+ this.urlEncoder = options.urlEncoder || ((p) => `file://${encodeURIComponent(p)}`);
882
+ this.getApplicationIcon = options.getApplicationIcon || null;
883
+ }
884
+ /**
885
+ * 获取缓存的缩略图 URL(不生成新的)
886
+ */
887
+ async getCachedThumbnailUrl(filePath) {
888
+ try {
889
+ const stats = await fs10.stat(filePath);
890
+ const fileType = getFileType(filePath, stats);
891
+ if (fileType === "application" /* APPLICATION */ && this.getApplicationIcon) {
892
+ return await this.getApplicationIcon(filePath);
893
+ }
894
+ if (fileType !== "image" /* IMAGE */ && fileType !== "video" /* VIDEO */) {
895
+ return null;
896
+ }
897
+ const mtime = stats.mtime.getTime();
898
+ const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);
899
+ if (cachedPath) {
900
+ return this.urlEncoder(cachedPath);
901
+ }
902
+ getFileHash(filePath).then((fileHash) => {
903
+ this.generateThumbnail(filePath, fileHash, mtime).catch(() => {
904
+ });
905
+ }).catch(() => {
906
+ });
907
+ return null;
908
+ } catch (error) {
909
+ return null;
910
+ }
911
+ }
912
+ /**
913
+ * 获取缩略图 URL(如果没有缓存则生成)
914
+ */
915
+ async getThumbnailUrl(filePath) {
916
+ try {
917
+ const stats = await fs10.stat(filePath);
918
+ const fileType = getFileType(filePath, stats);
919
+ if (fileType === "application" /* APPLICATION */ && this.getApplicationIcon) {
920
+ return await this.getApplicationIcon(filePath);
921
+ }
922
+ if (fileType !== "image" /* IMAGE */ && fileType !== "video" /* VIDEO */) {
923
+ return null;
924
+ }
925
+ const mtime = stats.mtime.getTime();
926
+ const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);
927
+ if (cachedPath) {
928
+ return this.urlEncoder(cachedPath);
929
+ }
930
+ const fileHash = await getFileHash(filePath);
931
+ const thumbnailPath = await this.generateThumbnail(filePath, fileHash, mtime);
932
+ if (thumbnailPath) {
933
+ return this.urlEncoder(thumbnailPath);
934
+ }
935
+ return null;
936
+ } catch (error) {
937
+ console.error(`Error getting thumbnail for ${filePath}:`, error);
938
+ return null;
939
+ }
940
+ }
941
+ /**
942
+ * 生成缩略图
943
+ */
944
+ async generateThumbnail(filePath, fileHash, mtime) {
945
+ const cachedPath = this.database.getThumbnailPath(filePath, fileHash, mtime);
946
+ if (cachedPath) {
947
+ return cachedPath;
948
+ }
949
+ try {
950
+ const ext = path10.extname(filePath).toLowerCase();
951
+ const hashPrefix = fileHash.substring(0, 16);
952
+ const thumbnailFileName = `${hashPrefix}.jpg`;
953
+ const thumbnailPath = path10.join(this.database.getCacheDir(), thumbnailFileName);
954
+ if (IMAGE_EXTENSIONS2.includes(ext) && this.imageProcessor) {
955
+ await this.imageProcessor.resize(filePath, thumbnailPath, 256);
956
+ } else if (VIDEO_EXTENSIONS2.includes(ext) && this.videoProcessor) {
957
+ await this.videoProcessor.screenshot(filePath, thumbnailPath, "00:00:01", "256x256");
958
+ } else {
959
+ return null;
960
+ }
961
+ this.database.saveThumbnail(filePath, fileHash, mtime, thumbnailPath);
962
+ return thumbnailPath;
963
+ } catch (error) {
964
+ console.debug(`Error generating thumbnail for ${filePath}:`, error);
965
+ return null;
966
+ }
967
+ }
968
+ /**
969
+ * 批量生成缩略图
970
+ */
971
+ async generateThumbnailsBatch(files) {
972
+ await Promise.allSettled(
973
+ files.map((file) => this.generateThumbnail(file.path, file.hash, file.mtime))
974
+ );
975
+ }
976
+ /**
977
+ * 删除缩略图
978
+ */
979
+ deleteThumbnail(filePath) {
980
+ this.database.deleteThumbnail(filePath);
981
+ }
982
+ /**
983
+ * 清理旧缩略图
984
+ */
985
+ cleanupOldThumbnails() {
986
+ this.database.cleanupOldThumbnails();
987
+ }
988
+ };
989
+ var thumbnailService = null;
990
+ function initThumbnailService(options) {
991
+ thumbnailService = new ThumbnailService(options);
992
+ return thumbnailService;
993
+ }
994
+ function getThumbnailService() {
995
+ return thumbnailService;
996
+ }
997
+
998
+ // src/thumbnail/database.ts
999
+ import Database from "better-sqlite3";
1000
+ import path11 from "path";
1001
+ import { existsSync, mkdirSync } from "fs";
1002
+ var SqliteThumbnailDatabase = class {
1003
+ db = null;
1004
+ cacheDir;
1005
+ constructor(userDataPath) {
1006
+ this.cacheDir = path11.join(userDataPath, "cache");
1007
+ if (!existsSync(this.cacheDir)) {
1008
+ mkdirSync(this.cacheDir, { recursive: true });
1009
+ }
1010
+ }
1011
+ /**
1012
+ * 初始化数据库
1013
+ */
1014
+ init() {
1015
+ if (this.db) return;
1016
+ const dbPath = path11.join(this.cacheDir, "thumbnails.db");
1017
+ this.db = new Database(dbPath, {
1018
+ fileMustExist: false
1019
+ });
1020
+ this.db.pragma("journal_mode = WAL");
1021
+ this.db.exec(`
1022
+ CREATE TABLE IF NOT EXISTS thumbnails (
1023
+ file_path TEXT PRIMARY KEY,
1024
+ file_hash TEXT NOT NULL,
1025
+ mtime INTEGER NOT NULL,
1026
+ thumbnail_path TEXT NOT NULL,
1027
+ created_at INTEGER NOT NULL
1028
+ );
1029
+
1030
+ CREATE INDEX IF NOT EXISTS idx_file_hash ON thumbnails(file_hash);
1031
+ CREATE INDEX IF NOT EXISTS idx_mtime ON thumbnails(mtime);
1032
+ `);
1033
+ }
1034
+ getCacheDir() {
1035
+ return this.cacheDir;
1036
+ }
1037
+ /**
1038
+ * 快速查询缩略图(用路径和修改时间,不需要哈希)
1039
+ * 比计算文件哈希快很多
1040
+ */
1041
+ getThumbnailPathFast(filePath, mtime) {
1042
+ if (!this.db) this.init();
1043
+ const stmt = this.db.prepare(`
1044
+ SELECT thumbnail_path
1045
+ FROM thumbnails
1046
+ WHERE file_path = ? AND mtime = ?
1047
+ `);
1048
+ const row = stmt.get(filePath, mtime);
1049
+ if (row && existsSync(row.thumbnail_path)) {
1050
+ return row.thumbnail_path;
1051
+ }
1052
+ return null;
1053
+ }
1054
+ getThumbnailPath(filePath, fileHash, mtime) {
1055
+ if (!this.db) this.init();
1056
+ const stmt = this.db.prepare(`
1057
+ SELECT thumbnail_path, mtime
1058
+ FROM thumbnails
1059
+ WHERE file_path = ? AND file_hash = ?
1060
+ `);
1061
+ const row = stmt.get(filePath, fileHash);
1062
+ if (row && row.mtime === mtime && existsSync(row.thumbnail_path)) {
1063
+ return row.thumbnail_path;
1064
+ }
1065
+ return null;
1066
+ }
1067
+ saveThumbnail(filePath, fileHash, mtime, thumbnailPath) {
1068
+ if (!this.db) this.init();
1069
+ const stmt = this.db.prepare(`
1070
+ INSERT OR REPLACE INTO thumbnails
1071
+ (file_path, file_hash, mtime, thumbnail_path, created_at)
1072
+ VALUES (?, ?, ?, ?, ?)
1073
+ `);
1074
+ stmt.run(filePath, fileHash, mtime, thumbnailPath, Date.now());
1075
+ }
1076
+ deleteThumbnail(filePath) {
1077
+ if (!this.db) this.init();
1078
+ const stmt = this.db.prepare("DELETE FROM thumbnails WHERE file_path = ?");
1079
+ stmt.run(filePath);
1080
+ }
1081
+ cleanupOldThumbnails() {
1082
+ if (!this.db) this.init();
1083
+ const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3;
1084
+ const stmt = this.db.prepare("DELETE FROM thumbnails WHERE created_at < ?");
1085
+ stmt.run(thirtyDaysAgo);
1086
+ }
1087
+ close() {
1088
+ if (this.db) {
1089
+ this.db.close();
1090
+ this.db = null;
1091
+ }
1092
+ }
1093
+ };
1094
+ var thumbnailDb = null;
1095
+ function createSqliteThumbnailDatabase(userDataPath) {
1096
+ if (!thumbnailDb) {
1097
+ thumbnailDb = new SqliteThumbnailDatabase(userDataPath);
1098
+ thumbnailDb.init();
1099
+ }
1100
+ return thumbnailDb;
1101
+ }
1102
+ function getSqliteThumbnailDatabase() {
1103
+ return thumbnailDb;
1104
+ }
1105
+
1106
+ // src/thumbnail/processors.ts
1107
+ import { execFile } from "child_process";
1108
+ import { promisify as promisify2 } from "util";
1109
+ var execFileAsync = promisify2(execFile);
1110
+ function createSharpImageProcessor(sharp) {
1111
+ return {
1112
+ async resize(filePath, outputPath, size) {
1113
+ await sharp(filePath).resize(size, size, {
1114
+ fit: "inside",
1115
+ withoutEnlargement: true
1116
+ }).jpeg({ quality: 85 }).toFile(outputPath);
1117
+ }
1118
+ };
1119
+ }
1120
+ function createFfmpegVideoProcessor(ffmpegPath) {
1121
+ return {
1122
+ async screenshot(filePath, outputPath, timestamp, size) {
1123
+ const scaleSize = size.replace("x", ":");
1124
+ await execFileAsync(ffmpegPath, [
1125
+ "-i",
1126
+ filePath,
1127
+ "-ss",
1128
+ timestamp,
1129
+ "-vframes",
1130
+ "1",
1131
+ "-vf",
1132
+ `scale=${scaleSize}:force_original_aspect_ratio=decrease`,
1133
+ "-y",
1134
+ outputPath
1135
+ ]);
1136
+ }
1137
+ };
1138
+ }
1139
+ export {
1140
+ FileType,
1141
+ SqliteThumbnailDatabase,
1142
+ ThumbnailService,
1143
+ copyFiles,
1144
+ copyFilesToClipboard,
1145
+ createFfmpegVideoProcessor,
1146
+ createFile,
1147
+ createFolder,
1148
+ createSharpImageProcessor,
1149
+ createSqliteThumbnailDatabase,
1150
+ deleteFiles,
1151
+ exists,
1152
+ formatDate,
1153
+ formatDateTime,
1154
+ formatFileSize,
1155
+ getAllSystemPaths,
1156
+ getApplicationIcon,
1157
+ getClipboardFiles,
1158
+ getFileHash,
1159
+ getFileHashes,
1160
+ getFileInfo,
1161
+ getFileType,
1162
+ getHomeDirectory,
1163
+ getPlatform,
1164
+ getSqliteThumbnailDatabase,
1165
+ getSystemPath,
1166
+ getThumbnailService,
1167
+ initThumbnailService,
1168
+ isDirectory,
1169
+ isMediaFile,
1170
+ isPreviewable,
1171
+ moveFiles,
1172
+ pasteFiles,
1173
+ readDirectory,
1174
+ readFileContent,
1175
+ readImageAsBase64,
1176
+ renameFile,
1177
+ searchFiles,
1178
+ searchFilesStream,
1179
+ searchFilesSync,
1180
+ writeFileContent
1181
+ };
1182
+ //# sourceMappingURL=index.js.map