@huyooo/file-explorer-core 0.4.18 → 0.4.21

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 CHANGED
@@ -1,2476 +1 @@
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/protocol.ts
18
- var APP_PROTOCOL_SCHEME = "app";
19
- var APP_PROTOCOL_HOST = "file";
20
- var APP_PROTOCOL_PREFIX = `${APP_PROTOCOL_SCHEME}://${APP_PROTOCOL_HOST}`;
21
- function encodeFileUrl(filePath) {
22
- if (!filePath) return "";
23
- const encodedPath = filePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
24
- return `${APP_PROTOCOL_PREFIX}${encodedPath}`;
25
- }
26
- function decodeFileUrl(url) {
27
- if (!url) return "";
28
- let path18 = url;
29
- if (path18.startsWith(APP_PROTOCOL_PREFIX)) {
30
- path18 = path18.slice(APP_PROTOCOL_PREFIX.length);
31
- }
32
- const hashIndex = path18.indexOf("#");
33
- if (hashIndex !== -1) {
34
- path18 = path18.substring(0, hashIndex);
35
- }
36
- const queryIndex = path18.indexOf("?");
37
- if (queryIndex !== -1) {
38
- path18 = path18.substring(0, queryIndex);
39
- }
40
- try {
41
- return decodeURIComponent(path18);
42
- } catch {
43
- return path18;
44
- }
45
- }
46
- function isAppProtocolUrl(url) {
47
- return url?.startsWith(APP_PROTOCOL_PREFIX) ?? false;
48
- }
49
-
50
- // src/utils/file-type.ts
51
- import path from "path";
52
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
53
- ".jpg",
54
- ".jpeg",
55
- ".png",
56
- ".gif",
57
- ".bmp",
58
- ".webp",
59
- ".svg",
60
- ".ico",
61
- ".tiff",
62
- ".tif",
63
- ".heic",
64
- ".heif"
65
- ]);
66
- var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
67
- ".mp4",
68
- ".mov",
69
- ".avi",
70
- ".mkv",
71
- ".wmv",
72
- ".flv",
73
- ".webm",
74
- ".m4v",
75
- ".3gp",
76
- ".mpeg",
77
- ".mpg"
78
- ]);
79
- var MUSIC_EXTENSIONS = /* @__PURE__ */ new Set([
80
- ".mp3",
81
- ".wav",
82
- ".flac",
83
- ".aac",
84
- ".ogg",
85
- ".wma",
86
- ".m4a",
87
- ".aiff",
88
- ".alac"
89
- ]);
90
- var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([
91
- ".pdf",
92
- ".doc",
93
- ".docx",
94
- ".xls",
95
- ".xlsx",
96
- ".ppt",
97
- ".pptx",
98
- ".odt",
99
- ".ods",
100
- ".odp",
101
- ".rtf",
102
- ".pages",
103
- ".numbers",
104
- ".key"
105
- ]);
106
- var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
107
- ".js",
108
- ".ts",
109
- ".jsx",
110
- ".tsx",
111
- ".vue",
112
- ".svelte",
113
- ".py",
114
- ".rb",
115
- ".go",
116
- ".rs",
117
- ".java",
118
- ".kt",
119
- ".swift",
120
- ".c",
121
- ".cpp",
122
- ".h",
123
- ".hpp",
124
- ".html",
125
- ".css",
126
- ".scss",
127
- ".sass",
128
- ".less",
129
- ".json",
130
- ".yaml",
131
- ".yml",
132
- ".toml",
133
- ".xml",
134
- ".sh",
135
- ".bash",
136
- ".zsh",
137
- ".fish",
138
- ".ps1",
139
- ".sql",
140
- ".graphql",
141
- ".prisma"
142
- ]);
143
- var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
144
- ".txt",
145
- ".md",
146
- ".markdown",
147
- ".log",
148
- ".ini",
149
- ".conf",
150
- ".cfg",
151
- ".env"
152
- ]);
153
- var ARCHIVE_EXTENSIONS = /* @__PURE__ */ new Set([
154
- ".zip",
155
- ".rar",
156
- ".7z",
157
- ".tar",
158
- ".gz",
159
- ".bz2",
160
- ".xz",
161
- ".dmg",
162
- ".iso"
163
- ]);
164
- var APPLICATION_EXTENSIONS = /* @__PURE__ */ new Set([
165
- ".app",
166
- ".exe",
167
- ".msi",
168
- ".deb",
169
- ".rpm",
170
- ".pkg",
171
- ".apk",
172
- ".ipa"
173
- ]);
174
- function getFileType(filePath, stats) {
175
- if (stats.isDirectory()) {
176
- if (filePath.endsWith(".app")) {
177
- return "application" /* APPLICATION */;
178
- }
179
- return "folder" /* FOLDER */;
180
- }
181
- const ext = path.extname(filePath).toLowerCase();
182
- if (IMAGE_EXTENSIONS.has(ext)) return "image" /* IMAGE */;
183
- if (VIDEO_EXTENSIONS.has(ext)) return "video" /* VIDEO */;
184
- if (MUSIC_EXTENSIONS.has(ext)) return "music" /* MUSIC */;
185
- if (DOCUMENT_EXTENSIONS.has(ext)) return "document" /* DOCUMENT */;
186
- if (CODE_EXTENSIONS.has(ext)) return "code" /* CODE */;
187
- if (TEXT_EXTENSIONS.has(ext)) return "text" /* TEXT */;
188
- if (ARCHIVE_EXTENSIONS.has(ext)) return "archive" /* ARCHIVE */;
189
- if (APPLICATION_EXTENSIONS.has(ext)) return "application" /* APPLICATION */;
190
- return "file" /* FILE */;
191
- }
192
- function isMediaFile(type) {
193
- return type === "image" /* IMAGE */ || type === "video" /* VIDEO */ || type === "music" /* MUSIC */;
194
- }
195
- function isPreviewable(type) {
196
- return type === "image" /* IMAGE */ || type === "video" /* VIDEO */ || type === "music" /* MUSIC */ || type === "text" /* TEXT */ || type === "code" /* CODE */;
197
- }
198
-
199
- // src/utils/formatters.ts
200
- function formatFileSize(bytes) {
201
- if (bytes === 0) return "0 B";
202
- const units = ["B", "KB", "MB", "GB", "TB"];
203
- const k = 1024;
204
- const i = Math.floor(Math.log(bytes) / Math.log(k));
205
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${units[i]}`;
206
- }
207
- function formatDate(date) {
208
- const now = /* @__PURE__ */ new Date();
209
- const diff = now.getTime() - date.getTime();
210
- const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
211
- if (days === 0) {
212
- return date.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
213
- } else if (days === 1) {
214
- return "\u6628\u5929";
215
- } else if (days < 7) {
216
- return `${days} \u5929\u524D`;
217
- } else {
218
- return date.toLocaleDateString("zh-CN", {
219
- year: "numeric",
220
- month: "2-digit",
221
- day: "2-digit"
222
- });
223
- }
224
- }
225
- function formatDateTime(date) {
226
- return date.toLocaleString("zh-CN", {
227
- year: "numeric",
228
- month: "2-digit",
229
- day: "2-digit",
230
- hour: "2-digit",
231
- minute: "2-digit"
232
- });
233
- }
234
-
235
- // src/operations/read.ts
236
- import { promises as fs } from "fs";
237
- import path2 from "path";
238
- var defaultUrlEncoder = (filePath) => `file://${encodeURIComponent(filePath)}`;
239
- async function readDirectory(dirPath, options = {}) {
240
- const {
241
- urlEncoder = defaultUrlEncoder,
242
- includeHidden = false,
243
- getThumbnailUrl
244
- } = options;
245
- try {
246
- const entries = await fs.readdir(dirPath, { withFileTypes: true });
247
- const items = [];
248
- for (const entry of entries) {
249
- if (!includeHidden && entry.name.startsWith(".")) {
250
- continue;
251
- }
252
- const fullPath = path2.join(dirPath, entry.name);
253
- try {
254
- let stats;
255
- try {
256
- stats = await fs.lstat(fullPath);
257
- } catch {
258
- try {
259
- stats = await fs.stat(fullPath);
260
- } catch (statError) {
261
- const err = statError;
262
- if (err.code !== "ENOENT") {
263
- console.warn(`Cannot access file ${fullPath}:`, err.message);
264
- }
265
- continue;
266
- }
267
- }
268
- const fileType = getFileType(fullPath, stats);
269
- const fileUrl = urlEncoder(fullPath);
270
- const item = {
271
- id: fullPath,
272
- name: entry.name,
273
- type: fileType,
274
- dateModified: formatDate(stats.mtime),
275
- url: fileUrl
276
- };
277
- if (stats.isDirectory()) {
278
- item.children = [];
279
- } else {
280
- item.size = formatFileSize(stats.size);
281
- if (getThumbnailUrl && (fileType === "image" /* IMAGE */ || fileType === "video" /* VIDEO */)) {
282
- const thumbUrl = await getThumbnailUrl(fullPath);
283
- if (thumbUrl) {
284
- item.thumbnailUrl = thumbUrl;
285
- }
286
- }
287
- }
288
- items.push(item);
289
- } catch (itemError) {
290
- const err = itemError;
291
- if (err.code !== "ENOENT") {
292
- console.warn(`Error processing file ${fullPath}:`, err.message);
293
- }
294
- continue;
295
- }
296
- }
297
- items.sort((a, b) => {
298
- if (a.type === "folder" /* FOLDER */ && b.type !== "folder" /* FOLDER */) return -1;
299
- if (a.type !== "folder" /* FOLDER */ && b.type === "folder" /* FOLDER */) return 1;
300
- return a.name.localeCompare(b.name, "zh-CN");
301
- });
302
- return items;
303
- } catch (error) {
304
- console.error(`Error reading directory ${dirPath}:`, error);
305
- return [];
306
- }
307
- }
308
- async function readFileContent(filePath) {
309
- return await fs.readFile(filePath, "utf-8");
310
- }
311
- async function readImageAsBase64(imagePath) {
312
- try {
313
- let actualPath = imagePath;
314
- if (imagePath.startsWith("app://file")) {
315
- const urlPath = imagePath.slice("app://file".length);
316
- actualPath = decodeURIComponent(urlPath);
317
- } else if (imagePath.startsWith("file://")) {
318
- actualPath = decodeURIComponent(imagePath.replace("file://", ""));
319
- }
320
- const stats = await fs.stat(actualPath);
321
- if (!stats.isFile()) {
322
- return { success: false, error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${actualPath}` };
323
- }
324
- const buffer = await fs.readFile(actualPath);
325
- const base64 = buffer.toString("base64");
326
- const ext = path2.extname(actualPath).toLowerCase().slice(1);
327
- const mimeTypes = {
328
- jpg: "image/jpeg",
329
- jpeg: "image/jpeg",
330
- png: "image/png",
331
- gif: "image/gif",
332
- webp: "image/webp",
333
- bmp: "image/bmp",
334
- svg: "image/svg+xml"
335
- };
336
- const mimeType = mimeTypes[ext] || "image/jpeg";
337
- return { success: true, base64, mimeType };
338
- } catch (error) {
339
- return { success: false, error: String(error) };
340
- }
341
- }
342
-
343
- // src/operations/write.ts
344
- import { promises as fs2 } from "fs";
345
- import path3 from "path";
346
- async function writeFileContent(filePath, content) {
347
- try {
348
- await fs2.writeFile(filePath, content, "utf-8");
349
- return { success: true };
350
- } catch (error) {
351
- return { success: false, error: String(error) };
352
- }
353
- }
354
- async function createFolder(folderPath) {
355
- try {
356
- try {
357
- await fs2.access(folderPath);
358
- } catch {
359
- await fs2.mkdir(folderPath, { recursive: true });
360
- return { success: true, data: { finalPath: folderPath } };
361
- }
362
- const dir = path3.dirname(folderPath);
363
- const baseName = path3.basename(folderPath);
364
- let counter = 2;
365
- let finalPath = path3.join(dir, `${baseName} ${counter}`);
366
- while (true) {
367
- try {
368
- await fs2.access(finalPath);
369
- counter++;
370
- finalPath = path3.join(dir, `${baseName} ${counter}`);
371
- } catch {
372
- break;
373
- }
374
- }
375
- await fs2.mkdir(finalPath, { recursive: true });
376
- return { success: true, data: { finalPath } };
377
- } catch (error) {
378
- return { success: false, error: String(error) };
379
- }
380
- }
381
- async function createFile(filePath, content = "") {
382
- try {
383
- const dir = path3.dirname(filePath);
384
- await fs2.mkdir(dir, { recursive: true });
385
- try {
386
- await fs2.access(filePath);
387
- } catch {
388
- await fs2.writeFile(filePath, content, "utf-8");
389
- return { success: true, data: { finalPath: filePath } };
390
- }
391
- const dirname3 = path3.dirname(filePath);
392
- const ext = path3.extname(filePath);
393
- const basename2 = path3.basename(filePath, ext);
394
- let counter = 2;
395
- let finalPath = path3.join(dirname3, `${basename2} ${counter}${ext}`);
396
- while (true) {
397
- try {
398
- await fs2.access(finalPath);
399
- counter++;
400
- finalPath = path3.join(dirname3, `${basename2} ${counter}${ext}`);
401
- } catch {
402
- break;
403
- }
404
- }
405
- await fs2.writeFile(finalPath, content, "utf-8");
406
- return { success: true, data: { finalPath } };
407
- } catch (error) {
408
- return { success: false, error: String(error) };
409
- }
410
- }
411
-
412
- // src/operations/delete.ts
413
- import { promises as fs3 } from "fs";
414
- async function deleteFiles(paths, options = {}) {
415
- const { adapter, useTrash = true, onDeleted } = options;
416
- try {
417
- for (const filePath of paths) {
418
- if (useTrash && adapter?.trashItem) {
419
- await adapter.trashItem(filePath);
420
- } else {
421
- const stats = await fs3.stat(filePath);
422
- if (stats.isDirectory()) {
423
- await fs3.rm(filePath, { recursive: true, force: true });
424
- } else {
425
- await fs3.unlink(filePath);
426
- }
427
- }
428
- onDeleted?.(filePath);
429
- }
430
- return {
431
- success: true,
432
- message: useTrash ? "\u6587\u4EF6\u5DF2\u79FB\u52A8\u5230\u56DE\u6536\u7AD9" : "\u6587\u4EF6\u5DF2\u5220\u9664"
433
- };
434
- } catch (error) {
435
- return { success: false, error: String(error) };
436
- }
437
- }
438
-
439
- // src/operations/rename.ts
440
- import { promises as fs4 } from "fs";
441
- async function renameFile(oldPath, newPath, options = {}) {
442
- const { onRenamed } = options;
443
- try {
444
- await fs4.rename(oldPath, newPath);
445
- onRenamed?.(oldPath, newPath);
446
- return { success: true };
447
- } catch (error) {
448
- return { success: false, error: String(error) };
449
- }
450
- }
451
-
452
- // src/operations/copy.ts
453
- import { promises as fs5 } from "fs";
454
- import path4 from "path";
455
- async function copyFiles(sourcePaths, targetDir) {
456
- try {
457
- const copiedPaths = [];
458
- for (const sourcePath of sourcePaths) {
459
- const fileName = path4.basename(sourcePath);
460
- let destPath = path4.join(targetDir, fileName);
461
- let counter = 1;
462
- while (true) {
463
- try {
464
- await fs5.access(destPath);
465
- const ext = path4.extname(fileName);
466
- const baseName = path4.basename(fileName, ext);
467
- destPath = path4.join(targetDir, `${baseName} ${++counter}${ext}`);
468
- } catch {
469
- break;
470
- }
471
- }
472
- const stats = await fs5.stat(sourcePath);
473
- if (stats.isDirectory()) {
474
- await copyDirectory(sourcePath, destPath);
475
- } else {
476
- await fs5.copyFile(sourcePath, destPath);
477
- }
478
- copiedPaths.push(destPath);
479
- }
480
- return { success: true, data: { copiedPaths } };
481
- } catch (error) {
482
- return { success: false, error: String(error) };
483
- }
484
- }
485
- async function moveFiles(sourcePaths, targetDir) {
486
- try {
487
- const movedPaths = [];
488
- for (const sourcePath of sourcePaths) {
489
- const fileName = path4.basename(sourcePath);
490
- let destPath = path4.join(targetDir, fileName);
491
- let counter = 1;
492
- while (true) {
493
- try {
494
- await fs5.access(destPath);
495
- const ext = path4.extname(fileName);
496
- const baseName = path4.basename(fileName, ext);
497
- destPath = path4.join(targetDir, `${baseName} ${++counter}${ext}`);
498
- } catch {
499
- break;
500
- }
501
- }
502
- try {
503
- await fs5.rename(sourcePath, destPath);
504
- } catch {
505
- const stats = await fs5.stat(sourcePath);
506
- if (stats.isDirectory()) {
507
- await copyDirectory(sourcePath, destPath);
508
- } else {
509
- await fs5.copyFile(sourcePath, destPath);
510
- }
511
- await fs5.rm(sourcePath, { recursive: true, force: true });
512
- }
513
- movedPaths.push(destPath);
514
- }
515
- return { success: true, data: { movedPaths } };
516
- } catch (error) {
517
- return { success: false, error: String(error) };
518
- }
519
- }
520
- async function copyDirectory(source, dest) {
521
- await fs5.mkdir(dest, { recursive: true });
522
- const entries = await fs5.readdir(source, { withFileTypes: true });
523
- for (const entry of entries) {
524
- const sourcePath = path4.join(source, entry.name);
525
- const destPath = path4.join(dest, entry.name);
526
- if (entry.isDirectory()) {
527
- await copyDirectory(sourcePath, destPath);
528
- } else {
529
- await fs5.copyFile(sourcePath, destPath);
530
- }
531
- }
532
- }
533
-
534
- // src/operations/info.ts
535
- import { promises as fs6 } from "fs";
536
- import path5 from "path";
537
- async function getFileInfo(filePath) {
538
- try {
539
- const stats = await fs6.stat(filePath);
540
- const name = path5.basename(filePath);
541
- const ext = path5.extname(name);
542
- return {
543
- success: true,
544
- data: {
545
- path: filePath,
546
- name,
547
- size: stats.size,
548
- isFile: stats.isFile(),
549
- isDirectory: stats.isDirectory(),
550
- createdAt: stats.birthtime,
551
- updatedAt: stats.mtime,
552
- extension: ext || void 0
553
- }
554
- };
555
- } catch (error) {
556
- return { success: false, error: String(error) };
557
- }
558
- }
559
- async function exists(filePath) {
560
- try {
561
- await fs6.access(filePath);
562
- return true;
563
- } catch {
564
- return false;
565
- }
566
- }
567
- async function isDirectory(filePath) {
568
- try {
569
- const stats = await fs6.stat(filePath);
570
- return stats.isDirectory();
571
- } catch {
572
- return false;
573
- }
574
- }
575
-
576
- // src/operations/shell.ts
577
- import { exec } from "child_process";
578
- import { promisify } from "util";
579
- import * as path6 from "path";
580
- var execAsync = promisify(exec);
581
- function getPlatform() {
582
- switch (process.platform) {
583
- case "darwin":
584
- return "mac";
585
- case "win32":
586
- return "windows";
587
- default:
588
- return "linux";
589
- }
590
- }
591
- async function showFileInfo(filePath) {
592
- const platform2 = getPlatform();
593
- try {
594
- switch (platform2) {
595
- case "mac": {
596
- const script = `tell application "Finder"
597
- activate
598
- set theFile to POSIX file "${filePath}" as alias
599
- open information window of theFile
600
- end tell`;
601
- await execAsync(`osascript -e '${script}'`);
602
- break;
603
- }
604
- case "windows": {
605
- const escapedPath = filePath.replace(/'/g, "''");
606
- const psScript = `
607
- $shell = New-Object -ComObject Shell.Application
608
- $folder = $shell.Namespace((Split-Path '${escapedPath}'))
609
- $item = $folder.ParseName((Split-Path '${escapedPath}' -Leaf))
610
- $item.InvokeVerb('properties')
611
- `;
612
- await execAsync(`powershell -Command "${psScript.replace(/\n/g, " ")}"`);
613
- break;
614
- }
615
- case "linux": {
616
- const commands = [
617
- `nautilus --select "${filePath}" && nautilus -q`,
618
- // GNOME
619
- `dolphin --select "${filePath}"`,
620
- // KDE
621
- `thunar --quit && thunar "${path6.dirname(filePath)}"`,
622
- // XFCE
623
- `xdg-open "${path6.dirname(filePath)}"`
624
- // 通用
625
- ];
626
- let lastError = null;
627
- for (const cmd of commands) {
628
- try {
629
- await execAsync(cmd);
630
- return { success: true };
631
- } catch (e) {
632
- lastError = e;
633
- }
634
- }
635
- throw lastError || new Error("No file manager found");
636
- }
637
- }
638
- return { success: true };
639
- } catch (error) {
640
- return {
641
- success: false,
642
- error: error instanceof Error ? error.message : String(error)
643
- };
644
- }
645
- }
646
- async function revealInFileManager(filePath) {
647
- const platform2 = getPlatform();
648
- try {
649
- switch (platform2) {
650
- case "mac": {
651
- await execAsync(`open -R "${filePath}"`);
652
- break;
653
- }
654
- case "windows": {
655
- await execAsync(`explorer.exe /select,"${filePath}"`);
656
- break;
657
- }
658
- case "linux": {
659
- await execAsync(`xdg-open "${path6.dirname(filePath)}"`);
660
- break;
661
- }
662
- }
663
- return { success: true };
664
- } catch (error) {
665
- return {
666
- success: false,
667
- error: error instanceof Error ? error.message : String(error)
668
- };
669
- }
670
- }
671
- async function openInTerminal(dirPath) {
672
- const platform2 = getPlatform();
673
- try {
674
- switch (platform2) {
675
- case "mac": {
676
- try {
677
- const itermScript = `tell application "iTerm"
678
- activate
679
- try
680
- set newWindow to (create window with default profile)
681
- tell current session of newWindow
682
- write text "cd '${dirPath}'"
683
- end tell
684
- on error
685
- tell current window
686
- create tab with default profile
687
- tell current session
688
- write text "cd '${dirPath}'"
689
- end tell
690
- end tell
691
- end try
692
- end tell`;
693
- await execAsync(`osascript -e '${itermScript}'`);
694
- } catch {
695
- const terminalScript = `tell application "Terminal"
696
- activate
697
- do script "cd '${dirPath}'"
698
- end tell`;
699
- await execAsync(`osascript -e '${terminalScript}'`);
700
- }
701
- break;
702
- }
703
- case "windows": {
704
- try {
705
- await execAsync(`wt -d "${dirPath}"`);
706
- } catch {
707
- await execAsync(`start cmd /K "cd /d ${dirPath}"`);
708
- }
709
- break;
710
- }
711
- case "linux": {
712
- const terminals = [
713
- `gnome-terminal --working-directory="${dirPath}"`,
714
- `konsole --workdir "${dirPath}"`,
715
- `xfce4-terminal --working-directory="${dirPath}"`,
716
- `xterm -e "cd '${dirPath}' && $SHELL"`
717
- ];
718
- let lastError = null;
719
- for (const cmd of terminals) {
720
- try {
721
- await execAsync(cmd);
722
- return { success: true };
723
- } catch (e) {
724
- lastError = e;
725
- }
726
- }
727
- throw lastError || new Error("No terminal found");
728
- }
729
- }
730
- return { success: true };
731
- } catch (error) {
732
- return {
733
- success: false,
734
- error: error instanceof Error ? error.message : String(error)
735
- };
736
- }
737
- }
738
- async function openInEditor(targetPath) {
739
- const platform2 = getPlatform();
740
- try {
741
- const editors = platform2 === "mac" ? [
742
- // macOS: 优先 Cursor,回退到 VSCode
743
- `open -a "Cursor" "${targetPath}"`,
744
- `open -a "Visual Studio Code" "${targetPath}"`,
745
- `code "${targetPath}"`
746
- ] : platform2 === "windows" ? [
747
- // Windows
748
- `cursor "${targetPath}"`,
749
- `code "${targetPath}"`
750
- ] : [
751
- // Linux
752
- `cursor "${targetPath}"`,
753
- `code "${targetPath}"`
754
- ];
755
- let lastError = null;
756
- for (const cmd of editors) {
757
- try {
758
- await execAsync(cmd);
759
- return { success: true };
760
- } catch (e) {
761
- lastError = e;
762
- }
763
- }
764
- throw lastError || new Error("No editor found");
765
- } catch (error) {
766
- return {
767
- success: false,
768
- error: error instanceof Error ? error.message : String(error)
769
- };
770
- }
771
- }
772
-
773
- // src/operations/compress.ts
774
- import * as compressing from "compressing";
775
- import * as path7 from "path";
776
- import * as fs7 from "fs";
777
- import { promises as fsPromises } from "fs";
778
- import { pipeline } from "stream/promises";
779
- function getExtension(format) {
780
- switch (format) {
781
- case "zip":
782
- return ".zip";
783
- case "tar":
784
- return ".tar";
785
- case "tgz":
786
- return ".tar.gz";
787
- case "tarbz2":
788
- return ".tar.bz2";
789
- default:
790
- return ".zip";
791
- }
792
- }
793
- function detectArchiveFormat(filePath) {
794
- const lowerPath = filePath.toLowerCase();
795
- if (lowerPath.endsWith(".zip")) return "zip";
796
- if (lowerPath.endsWith(".tar.gz") || lowerPath.endsWith(".tgz")) return "tgz";
797
- if (lowerPath.endsWith(".tar.bz2") || lowerPath.endsWith(".tbz2")) return "tarbz2";
798
- if (lowerPath.endsWith(".tar")) return "tar";
799
- return null;
800
- }
801
- function isArchiveFile(filePath) {
802
- return detectArchiveFormat(filePath) !== null;
803
- }
804
- async function getAllFiles(dirPath) {
805
- const files = [];
806
- const entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
807
- for (const entry of entries) {
808
- const fullPath = path7.join(dirPath, entry.name);
809
- if (entry.isDirectory()) {
810
- files.push(...await getAllFiles(fullPath));
811
- } else {
812
- files.push(fullPath);
813
- }
814
- }
815
- return files;
816
- }
817
- async function countFiles(sources) {
818
- let count = 0;
819
- for (const source of sources) {
820
- const stats = await fsPromises.stat(source);
821
- if (stats.isDirectory()) {
822
- const files = await getAllFiles(source);
823
- count += files.length;
824
- } else {
825
- count += 1;
826
- }
827
- }
828
- return count;
829
- }
830
- async function compressFiles(sources, options, onProgress) {
831
- try {
832
- const { format, level = "normal", outputName, outputDir, deleteSource } = options;
833
- const ext = getExtension(format);
834
- const finalName = outputName.endsWith(ext) ? outputName : outputName + ext;
835
- const outputPath = path7.join(outputDir, finalName);
836
- await fsPromises.mkdir(outputDir, { recursive: true });
837
- const totalCount = await countFiles(sources);
838
- let processedCount = 0;
839
- switch (format) {
840
- case "zip": {
841
- const stream = new compressing.zip.Stream();
842
- for (const source of sources) {
843
- const stats = await fsPromises.stat(source);
844
- const baseName = path7.basename(source);
845
- if (stats.isDirectory()) {
846
- const files = await getAllFiles(source);
847
- for (const file of files) {
848
- const relativePath = path7.relative(path7.dirname(source), file);
849
- stream.addEntry(file, { relativePath });
850
- processedCount++;
851
- onProgress?.({
852
- currentFile: path7.basename(file),
853
- processedCount,
854
- totalCount,
855
- percent: Math.round(processedCount / totalCount * 100)
856
- });
857
- }
858
- } else {
859
- stream.addEntry(source, { relativePath: baseName });
860
- processedCount++;
861
- onProgress?.({
862
- currentFile: baseName,
863
- processedCount,
864
- totalCount,
865
- percent: Math.round(processedCount / totalCount * 100)
866
- });
867
- }
868
- }
869
- const destStream = fs7.createWriteStream(outputPath);
870
- await pipeline(stream, destStream);
871
- break;
872
- }
873
- case "tar": {
874
- const stream = new compressing.tar.Stream();
875
- for (const source of sources) {
876
- const stats = await fsPromises.stat(source);
877
- const baseName = path7.basename(source);
878
- if (stats.isDirectory()) {
879
- const files = await getAllFiles(source);
880
- for (const file of files) {
881
- const relativePath = path7.relative(path7.dirname(source), file);
882
- stream.addEntry(file, { relativePath });
883
- processedCount++;
884
- onProgress?.({
885
- currentFile: path7.basename(file),
886
- processedCount,
887
- totalCount,
888
- percent: Math.round(processedCount / totalCount * 100)
889
- });
890
- }
891
- } else {
892
- stream.addEntry(source, { relativePath: baseName });
893
- processedCount++;
894
- onProgress?.({
895
- currentFile: baseName,
896
- processedCount,
897
- totalCount,
898
- percent: Math.round(processedCount / totalCount * 100)
899
- });
900
- }
901
- }
902
- const destStream = fs7.createWriteStream(outputPath);
903
- await pipeline(stream, destStream);
904
- break;
905
- }
906
- case "tgz": {
907
- const stream = new compressing.tgz.Stream();
908
- for (const source of sources) {
909
- const stats = await fsPromises.stat(source);
910
- const baseName = path7.basename(source);
911
- if (stats.isDirectory()) {
912
- const files = await getAllFiles(source);
913
- for (const file of files) {
914
- const relativePath = path7.relative(path7.dirname(source), file);
915
- stream.addEntry(file, { relativePath });
916
- processedCount++;
917
- onProgress?.({
918
- currentFile: path7.basename(file),
919
- processedCount,
920
- totalCount,
921
- percent: Math.round(processedCount / totalCount * 100)
922
- });
923
- }
924
- } else {
925
- stream.addEntry(source, { relativePath: baseName });
926
- processedCount++;
927
- onProgress?.({
928
- currentFile: baseName,
929
- processedCount,
930
- totalCount,
931
- percent: Math.round(processedCount / totalCount * 100)
932
- });
933
- }
934
- }
935
- const destStream = fs7.createWriteStream(outputPath);
936
- await pipeline(stream, destStream);
937
- break;
938
- }
939
- case "tarbz2": {
940
- console.warn("tar.bz2 format not fully supported, using tgz instead");
941
- const tgzStream = new compressing.tgz.Stream();
942
- for (const source of sources) {
943
- const stats = await fsPromises.stat(source);
944
- const baseName = path7.basename(source);
945
- if (stats.isDirectory()) {
946
- const files = await getAllFiles(source);
947
- for (const file of files) {
948
- const relativePath = path7.relative(path7.dirname(source), file);
949
- tgzStream.addEntry(file, { relativePath });
950
- processedCount++;
951
- onProgress?.({
952
- currentFile: path7.basename(file),
953
- processedCount,
954
- totalCount,
955
- percent: Math.round(processedCount / totalCount * 100)
956
- });
957
- }
958
- } else {
959
- tgzStream.addEntry(source, { relativePath: baseName });
960
- processedCount++;
961
- onProgress?.({
962
- currentFile: baseName,
963
- processedCount,
964
- totalCount,
965
- percent: Math.round(processedCount / totalCount * 100)
966
- });
967
- }
968
- }
969
- const destStream = fs7.createWriteStream(outputPath.replace(".tar.bz2", ".tar.gz"));
970
- await pipeline(tgzStream, destStream);
971
- break;
972
- }
973
- }
974
- if (deleteSource) {
975
- for (const source of sources) {
976
- const stats = await fsPromises.stat(source);
977
- if (stats.isDirectory()) {
978
- await fsPromises.rm(source, { recursive: true });
979
- } else {
980
- await fsPromises.unlink(source);
981
- }
982
- }
983
- }
984
- return { success: true, outputPath };
985
- } catch (error) {
986
- return {
987
- success: false,
988
- error: error instanceof Error ? error.message : String(error)
989
- };
990
- }
991
- }
992
- async function extractArchive(archivePath, options, onProgress) {
993
- try {
994
- const { targetDir, deleteArchive } = options;
995
- const format = detectArchiveFormat(archivePath);
996
- if (!format) {
997
- return { success: false, error: "\u4E0D\u652F\u6301\u7684\u538B\u7F29\u683C\u5F0F" };
998
- }
999
- await fsPromises.mkdir(targetDir, { recursive: true });
1000
- onProgress?.({
1001
- currentFile: path7.basename(archivePath),
1002
- processedCount: 0,
1003
- totalCount: 1,
1004
- percent: 0
1005
- });
1006
- switch (format) {
1007
- case "zip":
1008
- await compressing.zip.uncompress(archivePath, targetDir);
1009
- break;
1010
- case "tar":
1011
- await compressing.tar.uncompress(archivePath, targetDir);
1012
- break;
1013
- case "tgz":
1014
- await compressing.tgz.uncompress(archivePath, targetDir);
1015
- break;
1016
- case "tarbz2":
1017
- console.warn("tar.bz2 format not fully supported");
1018
- return { success: false, error: "tar.bz2 \u683C\u5F0F\u6682\u4E0D\u652F\u6301" };
1019
- }
1020
- onProgress?.({
1021
- currentFile: path7.basename(archivePath),
1022
- processedCount: 1,
1023
- totalCount: 1,
1024
- percent: 100
1025
- });
1026
- if (deleteArchive) {
1027
- await fsPromises.unlink(archivePath);
1028
- }
1029
- return { success: true, outputPath: targetDir };
1030
- } catch (error) {
1031
- return {
1032
- success: false,
1033
- error: error instanceof Error ? error.message : String(error)
1034
- };
1035
- }
1036
- }
1037
-
1038
- // src/system-paths.ts
1039
- import path8 from "path";
1040
- import os from "os";
1041
- var platform = process.platform;
1042
- function getSystemPath(pathId) {
1043
- const homeDir = os.homedir();
1044
- switch (pathId) {
1045
- case "desktop":
1046
- return path8.join(homeDir, "Desktop");
1047
- case "documents":
1048
- return path8.join(homeDir, "Documents");
1049
- case "downloads":
1050
- return path8.join(homeDir, "Downloads");
1051
- case "pictures":
1052
- return path8.join(homeDir, "Pictures");
1053
- case "music":
1054
- return path8.join(homeDir, "Music");
1055
- case "videos":
1056
- return platform === "darwin" ? path8.join(homeDir, "Movies") : path8.join(homeDir, "Videos");
1057
- case "applications":
1058
- return platform === "darwin" ? "/Applications" : platform === "win32" ? process.env.ProgramFiles || "C:\\Program Files" : "/usr/share/applications";
1059
- case "home":
1060
- return homeDir;
1061
- case "root":
1062
- return platform === "darwin" ? "/" : platform === "win32" ? "C:\\" : "/";
1063
- default:
1064
- return null;
1065
- }
1066
- }
1067
- function getAllSystemPaths() {
1068
- const pathIds = [
1069
- "desktop",
1070
- "documents",
1071
- "downloads",
1072
- "pictures",
1073
- "music",
1074
- "videos",
1075
- "applications",
1076
- "home",
1077
- "root"
1078
- ];
1079
- const result = {};
1080
- for (const id of pathIds) {
1081
- result[id] = getSystemPath(id);
1082
- }
1083
- return result;
1084
- }
1085
- function getHomeDirectory() {
1086
- return os.homedir();
1087
- }
1088
- function getPlatform2() {
1089
- return process.platform;
1090
- }
1091
-
1092
- // src/search.ts
1093
- import { fdir } from "fdir";
1094
- import path9 from "path";
1095
- import { promises as fs8 } from "fs";
1096
- function patternToRegex(pattern) {
1097
- return new RegExp(pattern.replace(/\*/g, ".*"), "i");
1098
- }
1099
- async function searchFiles(searchPath, pattern, maxDepth) {
1100
- const builder = new fdir().withFullPaths().withDirs();
1101
- if (maxDepth && maxDepth > 0) {
1102
- builder.withMaxDepth(maxDepth);
1103
- }
1104
- const api = builder.crawl(searchPath);
1105
- const files = await api.withPromise();
1106
- if (pattern) {
1107
- const regex = patternToRegex(pattern);
1108
- return files.filter((file) => regex.test(path9.basename(file)));
1109
- }
1110
- return files;
1111
- }
1112
- async function searchFilesStream(searchPath, pattern, onResults, maxResults = 100) {
1113
- const regex = patternToRegex(pattern);
1114
- const results = [];
1115
- let stopped = false;
1116
- async function searchDir(dirPath) {
1117
- if (stopped) return;
1118
- try {
1119
- const entries = await fs8.readdir(dirPath, { withFileTypes: true });
1120
- const matched = [];
1121
- const subdirs = [];
1122
- for (const entry of entries) {
1123
- if (stopped) return;
1124
- const fullPath = path9.join(dirPath, entry.name);
1125
- if (regex.test(entry.name)) {
1126
- matched.push(fullPath);
1127
- results.push(fullPath);
1128
- if (results.length >= maxResults) {
1129
- stopped = true;
1130
- onResults(matched, true);
1131
- return;
1132
- }
1133
- }
1134
- if (entry.isDirectory()) {
1135
- subdirs.push(fullPath);
1136
- }
1137
- }
1138
- if (matched.length > 0) {
1139
- onResults(matched, false);
1140
- }
1141
- for (const subdir of subdirs) {
1142
- await searchDir(subdir);
1143
- }
1144
- } catch {
1145
- }
1146
- }
1147
- await searchDir(searchPath);
1148
- if (!stopped) {
1149
- onResults([], true);
1150
- }
1151
- }
1152
- function searchFilesSync(searchPath, pattern, maxDepth) {
1153
- const builder = new fdir().withFullPaths().withDirs();
1154
- if (maxDepth && maxDepth > 0) {
1155
- builder.withMaxDepth(maxDepth);
1156
- }
1157
- const api = builder.crawl(searchPath);
1158
- const files = api.sync();
1159
- if (pattern) {
1160
- const regex = new RegExp(pattern.replace(/\*/g, ".*"), "i");
1161
- return files.filter((file) => regex.test(path9.basename(file)));
1162
- }
1163
- return files;
1164
- }
1165
-
1166
- // src/hash.ts
1167
- import { xxhash64 } from "hash-wasm";
1168
- import { stat } from "fs/promises";
1169
- async function getFileHash(filePath, stats) {
1170
- const fileStats = stats || await stat(filePath);
1171
- const hashInput = `${filePath}:${fileStats.size}:${fileStats.mtime.getTime()}`;
1172
- return await xxhash64(hashInput);
1173
- }
1174
-
1175
- // src/clipboard.ts
1176
- import { promises as fs9 } from "fs";
1177
- import path10 from "path";
1178
- async function copyFilesToClipboard(filePaths, clipboard) {
1179
- try {
1180
- const cleanPaths = [];
1181
- for (const p of filePaths) {
1182
- try {
1183
- await fs9.access(p);
1184
- cleanPaths.push(p);
1185
- } catch {
1186
- }
1187
- }
1188
- if (cleanPaths.length === 0) {
1189
- return { success: false, error: "\u6CA1\u6709\u627E\u5230\u6709\u6548\u7684\u6587\u4EF6" };
1190
- }
1191
- clipboard.writeFiles(cleanPaths);
1192
- return { success: true };
1193
- } catch (error) {
1194
- return { success: false, error: String(error) };
1195
- }
1196
- }
1197
- function getClipboardFiles(clipboard) {
1198
- try {
1199
- const files = clipboard.readFiles();
1200
- if (files && Array.isArray(files) && files.length > 0) {
1201
- return { success: true, data: { files } };
1202
- }
1203
- return { success: false, error: "\u526A\u8D34\u677F\u4E2D\u6CA1\u6709\u6587\u4EF6" };
1204
- } catch (error) {
1205
- return { success: false, error: String(error) };
1206
- }
1207
- }
1208
- async function pasteFiles(targetDir, sourcePaths) {
1209
- try {
1210
- if (!targetDir || typeof targetDir !== "string") {
1211
- return { success: false, error: "\u76EE\u6807\u76EE\u5F55\u8DEF\u5F84\u65E0\u6548" };
1212
- }
1213
- if (!sourcePaths || !Array.isArray(sourcePaths) || sourcePaths.length === 0) {
1214
- return { success: false, error: "\u6E90\u6587\u4EF6\u8DEF\u5F84\u5217\u8868\u65E0\u6548" };
1215
- }
1216
- const pastedPaths = [];
1217
- for (const sourcePath of sourcePaths) {
1218
- const fileName = path10.basename(sourcePath);
1219
- let destPath = path10.join(targetDir, fileName);
1220
- let counter = 1;
1221
- while (true) {
1222
- try {
1223
- await fs9.access(destPath);
1224
- const ext = path10.extname(fileName);
1225
- const baseName = path10.basename(fileName, ext);
1226
- destPath = path10.join(targetDir, `${baseName} ${++counter}${ext}`);
1227
- } catch {
1228
- break;
1229
- }
1230
- }
1231
- const stats = await fs9.stat(sourcePath);
1232
- if (stats.isDirectory()) {
1233
- await copyDirectory2(sourcePath, destPath);
1234
- } else {
1235
- await fs9.copyFile(sourcePath, destPath);
1236
- }
1237
- pastedPaths.push(destPath);
1238
- }
1239
- return { success: true, data: { pastedPaths } };
1240
- } catch (error) {
1241
- return { success: false, error: String(error) };
1242
- }
1243
- }
1244
- async function copyDirectory2(source, dest) {
1245
- await fs9.mkdir(dest, { recursive: true });
1246
- const entries = await fs9.readdir(source, { withFileTypes: true });
1247
- for (const entry of entries) {
1248
- const sourcePath = path10.join(source, entry.name);
1249
- const destPath = path10.join(dest, entry.name);
1250
- if (entry.isDirectory()) {
1251
- await copyDirectory2(sourcePath, destPath);
1252
- } else {
1253
- await fs9.copyFile(sourcePath, destPath);
1254
- }
1255
- }
1256
- }
1257
-
1258
- // src/application-icon.ts
1259
- import { promises as fs10 } from "fs";
1260
- import path11 from "path";
1261
- import os2 from "os";
1262
- import { exec as exec2 } from "child_process";
1263
- import { promisify as promisify2 } from "util";
1264
- var execAsync2 = promisify2(exec2);
1265
- async function findAppIconPath(appPath) {
1266
- const resourcesPath = path11.join(appPath, "Contents", "Resources");
1267
- try {
1268
- const commonIconNames = [
1269
- "AppIcon.icns",
1270
- "app.icns",
1271
- "application.icns",
1272
- "icon.icns"
1273
- ];
1274
- const infoPlistPath = path11.join(appPath, "Contents", "Info.plist");
1275
- try {
1276
- const infoPlistContent = await fs10.readFile(infoPlistPath, "utf-8");
1277
- const iconFileMatch = infoPlistContent.match(/<key>CFBundleIconFile<\/key>\s*<string>([^<]+)<\/string>/);
1278
- if (iconFileMatch && iconFileMatch[1]) {
1279
- let iconFileName = iconFileMatch[1].trim();
1280
- if (!iconFileName.endsWith(".icns")) {
1281
- iconFileName += ".icns";
1282
- }
1283
- const iconPath = path11.join(resourcesPath, iconFileName);
1284
- try {
1285
- await fs10.access(iconPath);
1286
- return iconPath;
1287
- } catch {
1288
- }
1289
- }
1290
- } catch {
1291
- }
1292
- for (const iconName of commonIconNames) {
1293
- const iconPath = path11.join(resourcesPath, iconName);
1294
- try {
1295
- await fs10.access(iconPath);
1296
- return iconPath;
1297
- } catch {
1298
- continue;
1299
- }
1300
- }
1301
- try {
1302
- const entries = await fs10.readdir(resourcesPath);
1303
- const icnsFile = entries.find((entry) => entry.toLowerCase().endsWith(".icns"));
1304
- if (icnsFile) {
1305
- return path11.join(resourcesPath, icnsFile);
1306
- }
1307
- } catch {
1308
- }
1309
- return null;
1310
- } catch {
1311
- return null;
1312
- }
1313
- }
1314
- async function getApplicationIcon(appPath) {
1315
- if (process.platform !== "darwin") {
1316
- return null;
1317
- }
1318
- try {
1319
- const stats = await fs10.stat(appPath);
1320
- if (!stats.isDirectory() || !appPath.endsWith(".app")) {
1321
- return null;
1322
- }
1323
- } catch {
1324
- return null;
1325
- }
1326
- const iconPath = await findAppIconPath(appPath);
1327
- if (!iconPath) {
1328
- return null;
1329
- }
1330
- try {
1331
- const tempPngPath = path11.join(os2.tmpdir(), `app-icon-${Date.now()}.png`);
1332
- await execAsync2(
1333
- `sips -s format png "${iconPath}" --out "${tempPngPath}" --resampleHeightWidthMax 128`
1334
- );
1335
- const pngBuffer = await fs10.readFile(tempPngPath);
1336
- try {
1337
- await fs10.unlink(tempPngPath);
1338
- } catch {
1339
- }
1340
- const base64 = pngBuffer.toString("base64");
1341
- return `data:image/png;base64,${base64}`;
1342
- } catch {
1343
- return null;
1344
- }
1345
- }
1346
-
1347
- // src/watch.ts
1348
- import * as fs11 from "fs";
1349
- import * as path12 from "path";
1350
- var debounceTimers = /* @__PURE__ */ new Map();
1351
- var DEBOUNCE_DELAY = 100;
1352
- function watchDirectory(dirPath, callback) {
1353
- let watcher = null;
1354
- try {
1355
- watcher = fs11.watch(dirPath, { persistent: true }, (eventType, filename) => {
1356
- if (!filename) return;
1357
- const fullPath = path12.join(dirPath, filename);
1358
- const key = `${dirPath}:${filename}`;
1359
- const existingTimer = debounceTimers.get(key);
1360
- if (existingTimer) {
1361
- clearTimeout(existingTimer);
1362
- }
1363
- const timer = setTimeout(() => {
1364
- debounceTimers.delete(key);
1365
- fs11.access(fullPath, fs11.constants.F_OK, (err) => {
1366
- let type;
1367
- if (err) {
1368
- type = "remove";
1369
- } else if (eventType === "rename") {
1370
- type = "add";
1371
- } else {
1372
- type = "change";
1373
- }
1374
- callback({
1375
- type,
1376
- path: fullPath,
1377
- filename
1378
- });
1379
- });
1380
- }, DEBOUNCE_DELAY);
1381
- debounceTimers.set(key, timer);
1382
- });
1383
- watcher.on("error", (error) => {
1384
- console.error("Watch error:", error);
1385
- });
1386
- } catch (error) {
1387
- console.error("Failed to watch directory:", error);
1388
- }
1389
- return {
1390
- close: () => {
1391
- if (watcher) {
1392
- watcher.close();
1393
- watcher = null;
1394
- }
1395
- for (const [key, timer] of debounceTimers.entries()) {
1396
- if (key.startsWith(`${dirPath}:`)) {
1397
- clearTimeout(timer);
1398
- debounceTimers.delete(key);
1399
- }
1400
- }
1401
- },
1402
- path: dirPath
1403
- };
1404
- }
1405
- var WatchManager = class {
1406
- watchers = /* @__PURE__ */ new Map();
1407
- callbacks = /* @__PURE__ */ new Map();
1408
- /**
1409
- * 开始监听目录
1410
- */
1411
- watch(dirPath, callback) {
1412
- const normalizedPath = path12.normalize(dirPath);
1413
- let callbackSet = this.callbacks.get(normalizedPath);
1414
- if (!callbackSet) {
1415
- callbackSet = /* @__PURE__ */ new Set();
1416
- this.callbacks.set(normalizedPath, callbackSet);
1417
- }
1418
- callbackSet.add(callback);
1419
- let watcherInfo = this.watchers.get(normalizedPath);
1420
- if (watcherInfo) {
1421
- watcherInfo.refCount++;
1422
- } else {
1423
- const watcher = watchDirectory(normalizedPath, (event) => {
1424
- const callbacks = this.callbacks.get(normalizedPath);
1425
- if (callbacks) {
1426
- for (const cb of callbacks) {
1427
- try {
1428
- cb(event);
1429
- } catch (error) {
1430
- console.error("Watch callback error:", error);
1431
- }
1432
- }
1433
- }
1434
- });
1435
- watcherInfo = { watcher, refCount: 1 };
1436
- this.watchers.set(normalizedPath, watcherInfo);
1437
- }
1438
- return () => {
1439
- this.unwatch(normalizedPath, callback);
1440
- };
1441
- }
1442
- /**
1443
- * 停止监听
1444
- */
1445
- unwatch(dirPath, callback) {
1446
- const normalizedPath = path12.normalize(dirPath);
1447
- const callbackSet = this.callbacks.get(normalizedPath);
1448
- if (callbackSet) {
1449
- callbackSet.delete(callback);
1450
- if (callbackSet.size === 0) {
1451
- this.callbacks.delete(normalizedPath);
1452
- }
1453
- }
1454
- const watcherInfo = this.watchers.get(normalizedPath);
1455
- if (watcherInfo) {
1456
- watcherInfo.refCount--;
1457
- if (watcherInfo.refCount <= 0) {
1458
- watcherInfo.watcher.close();
1459
- this.watchers.delete(normalizedPath);
1460
- }
1461
- }
1462
- }
1463
- /**
1464
- * 关闭所有监听器
1465
- */
1466
- closeAll() {
1467
- for (const [, watcherInfo] of this.watchers) {
1468
- watcherInfo.watcher.close();
1469
- }
1470
- this.watchers.clear();
1471
- this.callbacks.clear();
1472
- }
1473
- };
1474
- var globalWatchManager = null;
1475
- function getWatchManager() {
1476
- if (!globalWatchManager) {
1477
- globalWatchManager = new WatchManager();
1478
- }
1479
- return globalWatchManager;
1480
- }
1481
-
1482
- // src/thumbnail/service.ts
1483
- import { promises as fs12 } from "fs";
1484
- import path13 from "path";
1485
- function isImageFile(filePath, fileType) {
1486
- return fileType === "image" /* IMAGE */;
1487
- }
1488
- function isVideoFile(filePath, fileType) {
1489
- return fileType === "video" /* VIDEO */;
1490
- }
1491
- var ThumbnailService = class {
1492
- database;
1493
- imageProcessor;
1494
- videoProcessor;
1495
- urlEncoder;
1496
- getApplicationIcon;
1497
- constructor(options) {
1498
- this.database = options.database;
1499
- this.imageProcessor = options.imageProcessor || null;
1500
- this.videoProcessor = options.videoProcessor || null;
1501
- this.urlEncoder = options.urlEncoder || ((p) => `file://${encodeURIComponent(p)}`);
1502
- this.getApplicationIcon = options.getApplicationIcon || null;
1503
- }
1504
- /**
1505
- * 获取缓存的缩略图 URL(不生成新的)
1506
- */
1507
- async getCachedThumbnailUrl(filePath) {
1508
- try {
1509
- const stats = await fs12.stat(filePath);
1510
- const fileType = getFileType(filePath, stats);
1511
- if (fileType === "application" /* APPLICATION */ && this.getApplicationIcon) {
1512
- return await this.getApplicationIcon(filePath);
1513
- }
1514
- if (fileType !== "image" /* IMAGE */ && fileType !== "video" /* VIDEO */) {
1515
- return null;
1516
- }
1517
- const mtime = stats.mtime.getTime();
1518
- const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);
1519
- if (cachedPath) {
1520
- return this.urlEncoder(cachedPath);
1521
- }
1522
- getFileHash(filePath, stats).then((fileHash) => {
1523
- this.generateThumbnail(filePath, fileHash, mtime).catch(() => {
1524
- });
1525
- }).catch(() => {
1526
- });
1527
- return null;
1528
- } catch (error) {
1529
- return null;
1530
- }
1531
- }
1532
- /**
1533
- * 获取缩略图 URL(如果没有缓存则生成)
1534
- */
1535
- async getThumbnailUrl(filePath) {
1536
- try {
1537
- const stats = await fs12.stat(filePath);
1538
- const fileType = getFileType(filePath, stats);
1539
- if (fileType === "application" /* APPLICATION */ && this.getApplicationIcon) {
1540
- return await this.getApplicationIcon(filePath);
1541
- }
1542
- if (fileType !== "image" /* IMAGE */ && fileType !== "video" /* VIDEO */) {
1543
- return null;
1544
- }
1545
- const mtime = stats.mtime.getTime();
1546
- const cachedPath = this.database.getThumbnailPathFast(filePath, mtime);
1547
- if (cachedPath) {
1548
- return this.urlEncoder(cachedPath);
1549
- }
1550
- const fileHash = await getFileHash(filePath, stats);
1551
- const thumbnailPath = await this.generateThumbnail(filePath, fileHash, mtime);
1552
- if (thumbnailPath) {
1553
- return this.urlEncoder(thumbnailPath);
1554
- }
1555
- return null;
1556
- } catch (error) {
1557
- console.error(`Error getting thumbnail for ${filePath}:`, error);
1558
- return null;
1559
- }
1560
- }
1561
- /**
1562
- * 生成缩略图
1563
- */
1564
- async generateThumbnail(filePath, fileHash, mtime) {
1565
- const cachedPath = this.database.getThumbnailPath(filePath, fileHash, mtime);
1566
- if (cachedPath) {
1567
- return cachedPath;
1568
- }
1569
- try {
1570
- const stats = await fs12.stat(filePath);
1571
- const fileType = getFileType(filePath, stats);
1572
- const hashPrefix = fileHash.substring(0, 16);
1573
- const thumbnailFileName = `${hashPrefix}.jpg`;
1574
- const thumbnailPath = path13.join(this.database.getCacheDir(), thumbnailFileName);
1575
- if (isImageFile(filePath, fileType) && this.imageProcessor) {
1576
- await this.imageProcessor.resize(filePath, thumbnailPath, 256);
1577
- } else if (isVideoFile(filePath, fileType) && this.videoProcessor) {
1578
- await this.videoProcessor.screenshot(filePath, thumbnailPath, "00:00:01", "256x256");
1579
- } else {
1580
- return null;
1581
- }
1582
- this.database.saveThumbnail(filePath, fileHash, mtime, thumbnailPath);
1583
- return thumbnailPath;
1584
- } catch (error) {
1585
- console.debug(`Error generating thumbnail for ${filePath}:`, error);
1586
- return null;
1587
- }
1588
- }
1589
- /**
1590
- * 批量生成缩略图(带并发限制,避免资源耗尽)
1591
- */
1592
- async generateThumbnailsBatch(files, concurrency = 3) {
1593
- const execute = async (file) => {
1594
- try {
1595
- await this.generateThumbnail(file.path, file.hash, file.mtime);
1596
- } catch (error) {
1597
- console.debug(`Failed to generate thumbnail for ${file.path}:`, error);
1598
- }
1599
- };
1600
- for (let i = 0; i < files.length; i += concurrency) {
1601
- const batch = files.slice(i, i + concurrency);
1602
- await Promise.allSettled(batch.map(execute));
1603
- }
1604
- }
1605
- /**
1606
- * 删除缩略图
1607
- */
1608
- deleteThumbnail(filePath) {
1609
- this.database.deleteThumbnail(filePath);
1610
- }
1611
- /**
1612
- * 清理旧缩略图
1613
- */
1614
- cleanupOldThumbnails() {
1615
- this.database.cleanupOldThumbnails();
1616
- }
1617
- };
1618
- var thumbnailService = null;
1619
- function initThumbnailService(options) {
1620
- thumbnailService = new ThumbnailService(options);
1621
- return thumbnailService;
1622
- }
1623
- function getThumbnailService() {
1624
- return thumbnailService;
1625
- }
1626
-
1627
- // src/thumbnail/database.ts
1628
- import Database from "better-sqlite3";
1629
- import path14 from "path";
1630
- import { existsSync, mkdirSync } from "fs";
1631
- var SqliteThumbnailDatabase = class {
1632
- db = null;
1633
- cacheDir;
1634
- dbPath;
1635
- constructor(userDataPath, options = {}) {
1636
- const defaultDirName = options.dirName || "thumbnails";
1637
- const defaultDbFileName = options.dbFileName || "thumbnails.db";
1638
- const inferredDirFromDbPath = options.dbPath ? path14.dirname(options.dbPath) : null;
1639
- this.cacheDir = options.thumbnailDir ? options.thumbnailDir : inferredDirFromDbPath || path14.join(userDataPath, defaultDirName);
1640
- this.dbPath = options.dbPath ? options.dbPath : path14.join(this.cacheDir, defaultDbFileName);
1641
- if (!existsSync(this.cacheDir)) {
1642
- mkdirSync(this.cacheDir, { recursive: true });
1643
- }
1644
- }
1645
- /**
1646
- * 初始化数据库
1647
- */
1648
- init() {
1649
- if (this.db) return;
1650
- this.db = new Database(this.dbPath, {
1651
- fileMustExist: false
1652
- });
1653
- this.db.pragma("journal_mode = WAL");
1654
- this.db.exec(`
1655
- CREATE TABLE IF NOT EXISTS thumbnails (
1656
- file_path TEXT PRIMARY KEY,
1657
- file_hash TEXT NOT NULL,
1658
- mtime INTEGER NOT NULL,
1659
- thumbnail_path TEXT NOT NULL,
1660
- created_at INTEGER NOT NULL
1661
- );
1662
-
1663
- CREATE INDEX IF NOT EXISTS idx_file_hash ON thumbnails(file_hash);
1664
- CREATE INDEX IF NOT EXISTS idx_mtime ON thumbnails(mtime);
1665
- `);
1666
- }
1667
- getCacheDir() {
1668
- return this.cacheDir;
1669
- }
1670
- /**
1671
- * 快速查询缩略图(用路径和修改时间,不需要哈希)
1672
- * 比计算文件哈希快很多
1673
- */
1674
- getThumbnailPathFast(filePath, mtime) {
1675
- if (!this.db) this.init();
1676
- const stmt = this.db.prepare(`
1677
- SELECT thumbnail_path
1678
- FROM thumbnails
1679
- WHERE file_path = ? AND mtime = ?
1680
- `);
1681
- const row = stmt.get(filePath, mtime);
1682
- if (row && existsSync(row.thumbnail_path)) {
1683
- return row.thumbnail_path;
1684
- }
1685
- return null;
1686
- }
1687
- getThumbnailPath(filePath, fileHash, mtime) {
1688
- if (!this.db) this.init();
1689
- const stmt = this.db.prepare(`
1690
- SELECT thumbnail_path, mtime
1691
- FROM thumbnails
1692
- WHERE file_path = ? AND file_hash = ?
1693
- `);
1694
- const row = stmt.get(filePath, fileHash);
1695
- if (row && row.mtime === mtime && existsSync(row.thumbnail_path)) {
1696
- return row.thumbnail_path;
1697
- }
1698
- return null;
1699
- }
1700
- saveThumbnail(filePath, fileHash, mtime, thumbnailPath) {
1701
- if (!this.db) this.init();
1702
- const stmt = this.db.prepare(`
1703
- INSERT OR REPLACE INTO thumbnails
1704
- (file_path, file_hash, mtime, thumbnail_path, created_at)
1705
- VALUES (?, ?, ?, ?, ?)
1706
- `);
1707
- stmt.run(filePath, fileHash, mtime, thumbnailPath, Date.now());
1708
- }
1709
- deleteThumbnail(filePath) {
1710
- if (!this.db) this.init();
1711
- const stmt = this.db.prepare("DELETE FROM thumbnails WHERE file_path = ?");
1712
- stmt.run(filePath);
1713
- }
1714
- cleanupOldThumbnails() {
1715
- if (!this.db) this.init();
1716
- const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3;
1717
- const stmt = this.db.prepare("DELETE FROM thumbnails WHERE created_at < ?");
1718
- stmt.run(thirtyDaysAgo);
1719
- }
1720
- close() {
1721
- if (this.db) {
1722
- try {
1723
- this.db.pragma("wal_checkpoint(TRUNCATE)");
1724
- } catch (error) {
1725
- }
1726
- this.db.close();
1727
- this.db = null;
1728
- }
1729
- }
1730
- };
1731
- var thumbnailDb = null;
1732
- function createSqliteThumbnailDatabase(options) {
1733
- if (!thumbnailDb) {
1734
- thumbnailDb = new SqliteThumbnailDatabase(options.userDataPath, options);
1735
- thumbnailDb.init();
1736
- }
1737
- return thumbnailDb;
1738
- }
1739
- function getSqliteThumbnailDatabase() {
1740
- return thumbnailDb;
1741
- }
1742
- function closeThumbnailDatabase() {
1743
- if (thumbnailDb) {
1744
- thumbnailDb.close();
1745
- thumbnailDb = null;
1746
- }
1747
- }
1748
-
1749
- // src/thumbnail/processors.ts
1750
- import { spawn } from "child_process";
1751
- function createSharpImageProcessor(sharp) {
1752
- return {
1753
- async resize(filePath, outputPath, size) {
1754
- await sharp(filePath).resize({
1755
- width: size,
1756
- withoutEnlargement: true
1757
- }).jpeg({
1758
- quality: 80,
1759
- optimiseCoding: true
1760
- // 优化编码,提升压缩率
1761
- }).toFile(outputPath);
1762
- }
1763
- };
1764
- }
1765
- function createFfmpegVideoProcessor(ffmpegPath) {
1766
- return {
1767
- async screenshot(filePath, outputPath, timestamp, size) {
1768
- const width = size.split("x")[0];
1769
- return new Promise((resolve, reject) => {
1770
- const ffmpeg = spawn(ffmpegPath, [
1771
- "-y",
1772
- "-ss",
1773
- timestamp,
1774
- "-i",
1775
- filePath,
1776
- "-vframes",
1777
- "1",
1778
- "-vf",
1779
- `thumbnail,scale=${width}:-1:force_original_aspect_ratio=decrease`,
1780
- // 使用 mjpeg 编码器,性能更好(借鉴 pixflow)
1781
- "-c:v",
1782
- "mjpeg",
1783
- "-q:v",
1784
- "6",
1785
- // 质量参数,6 表示中等质量(范围 2-31,数值越小质量越高)
1786
- outputPath
1787
- ]);
1788
- const timeout = setTimeout(() => {
1789
- ffmpeg.kill();
1790
- reject(new Error("Video thumbnail generation timeout"));
1791
- }, 3e4);
1792
- ffmpeg.stderr.on("data", (data) => {
1793
- const output = data.toString();
1794
- if (output.includes("Unsupported pixel format")) {
1795
- return;
1796
- }
1797
- });
1798
- ffmpeg.on("close", (code) => {
1799
- clearTimeout(timeout);
1800
- if (code === 0) {
1801
- resolve();
1802
- } else {
1803
- reject(new Error(`ffmpeg exited with code ${code}`));
1804
- }
1805
- });
1806
- ffmpeg.on("error", (error) => {
1807
- clearTimeout(timeout);
1808
- reject(error);
1809
- });
1810
- });
1811
- }
1812
- };
1813
- }
1814
-
1815
- // src/media/format-detector.ts
1816
- import { execFile } from "child_process";
1817
- import { promisify as promisify3 } from "util";
1818
- import path15 from "path";
1819
- var execFileAsync = promisify3(execFile);
1820
- var BROWSER_VIDEO_CONTAINERS = /* @__PURE__ */ new Set(["mp4", "webm", "ogg", "ogv"]);
1821
- var BROWSER_VIDEO_CODECS = /* @__PURE__ */ new Set(["h264", "avc1", "vp8", "vp9", "theora", "av1"]);
1822
- var BROWSER_AUDIO_CODECS = /* @__PURE__ */ new Set(["aac", "mp3", "opus", "vorbis", "flac"]);
1823
- var BROWSER_AUDIO_CONTAINERS = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "oga", "webm", "m4a", "aac", "flac"]);
1824
- var BROWSER_AUDIO_ONLY_CODECS = /* @__PURE__ */ new Set(["mp3", "aac", "opus", "vorbis", "flac", "pcm_s16le", "pcm_s24le"]);
1825
- var REMUXABLE_VIDEO_CODECS = /* @__PURE__ */ new Set(["h264", "avc1", "hevc", "h265"]);
1826
- var REMUXABLE_AUDIO_CODECS = /* @__PURE__ */ new Set(["aac", "alac"]);
1827
- var VIDEO_EXTENSIONS2 = /* @__PURE__ */ new Set([
1828
- "mp4",
1829
- "mkv",
1830
- "avi",
1831
- "mov",
1832
- "wmv",
1833
- "flv",
1834
- "webm",
1835
- "ogv",
1836
- "ogg",
1837
- "m4v",
1838
- "mpeg",
1839
- "mpg",
1840
- "3gp",
1841
- "ts",
1842
- "mts",
1843
- "m2ts",
1844
- "vob",
1845
- "rmvb",
1846
- "rm"
1847
- ]);
1848
- var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
1849
- "mp3",
1850
- "wav",
1851
- "flac",
1852
- "aac",
1853
- "m4a",
1854
- "ogg",
1855
- "oga",
1856
- "wma",
1857
- "ape",
1858
- "alac",
1859
- "aiff",
1860
- "aif",
1861
- "opus",
1862
- "mid",
1863
- "midi",
1864
- "wv",
1865
- "mka"
1866
- ]);
1867
- function getMediaTypeByExtension(filePath) {
1868
- const ext = path15.extname(filePath).toLowerCase().slice(1);
1869
- if (VIDEO_EXTENSIONS2.has(ext)) return "video";
1870
- if (AUDIO_EXTENSIONS.has(ext)) return "audio";
1871
- return null;
1872
- }
1873
- async function getMediaFormat(filePath, ffprobePath) {
1874
- try {
1875
- const { stdout } = await execFileAsync(ffprobePath, [
1876
- "-v",
1877
- "quiet",
1878
- "-print_format",
1879
- "json",
1880
- "-show_format",
1881
- "-show_streams",
1882
- filePath
1883
- ]);
1884
- const data = JSON.parse(stdout);
1885
- const format = data.format || {};
1886
- const streams = data.streams || [];
1887
- const videoStream = streams.find((s) => s.codec_type === "video");
1888
- const audioStream = streams.find((s) => s.codec_type === "audio");
1889
- const type = videoStream ? "video" : "audio";
1890
- const formatName = format.format_name || "";
1891
- const container = formatName.split(",")[0].toLowerCase();
1892
- return {
1893
- type,
1894
- container,
1895
- videoCodec: videoStream?.codec_name?.toLowerCase(),
1896
- audioCodec: audioStream?.codec_name?.toLowerCase(),
1897
- duration: parseFloat(format.duration) || void 0,
1898
- width: videoStream?.width,
1899
- height: videoStream?.height,
1900
- bitrate: parseInt(format.bit_rate) || void 0
1901
- };
1902
- } catch {
1903
- const type = getMediaTypeByExtension(filePath);
1904
- if (!type) return null;
1905
- const ext = path15.extname(filePath).toLowerCase().slice(1);
1906
- return {
1907
- type,
1908
- container: ext
1909
- };
1910
- }
1911
- }
1912
- function canPlayVideoDirectly(format) {
1913
- if (!BROWSER_VIDEO_CONTAINERS.has(format.container)) {
1914
- return false;
1915
- }
1916
- if (format.videoCodec && !BROWSER_VIDEO_CODECS.has(format.videoCodec)) {
1917
- return false;
1918
- }
1919
- if (format.audioCodec && !BROWSER_AUDIO_CODECS.has(format.audioCodec)) {
1920
- return false;
1921
- }
1922
- return true;
1923
- }
1924
- function canPlayAudioDirectly(format) {
1925
- if (!BROWSER_AUDIO_CONTAINERS.has(format.container)) {
1926
- return false;
1927
- }
1928
- if (format.audioCodec && !BROWSER_AUDIO_ONLY_CODECS.has(format.audioCodec)) {
1929
- return false;
1930
- }
1931
- return true;
1932
- }
1933
- function canRemuxVideo(format) {
1934
- if (!format.videoCodec || !REMUXABLE_VIDEO_CODECS.has(format.videoCodec)) {
1935
- return false;
1936
- }
1937
- if (format.audioCodec) {
1938
- const audioOk = BROWSER_AUDIO_CODECS.has(format.audioCodec) || REMUXABLE_AUDIO_CODECS.has(format.audioCodec);
1939
- if (!audioOk) {
1940
- return false;
1941
- }
1942
- }
1943
- return true;
1944
- }
1945
- function canRemuxAudio(format) {
1946
- return format.audioCodec ? REMUXABLE_AUDIO_CODECS.has(format.audioCodec) : false;
1947
- }
1948
- function estimateTranscodeTime(duration, method) {
1949
- if (!duration || method === "direct") return void 0;
1950
- if (method === "remux") {
1951
- return Math.ceil(duration / 50);
1952
- }
1953
- return Math.ceil(duration / 3);
1954
- }
1955
- async function detectTranscodeNeeds(filePath, ffprobePath) {
1956
- const formatInfo = await getMediaFormat(filePath, ffprobePath);
1957
- if (!formatInfo) {
1958
- const type2 = getMediaTypeByExtension(filePath) || "video";
1959
- return {
1960
- type: type2,
1961
- needsTranscode: false,
1962
- method: "direct"
1963
- };
1964
- }
1965
- const { type } = formatInfo;
1966
- if (type === "video") {
1967
- if (canPlayVideoDirectly(formatInfo)) {
1968
- return {
1969
- type,
1970
- needsTranscode: false,
1971
- method: "direct",
1972
- formatInfo
1973
- };
1974
- }
1975
- if (canRemuxVideo(formatInfo)) {
1976
- return {
1977
- type,
1978
- needsTranscode: true,
1979
- method: "remux",
1980
- formatInfo,
1981
- targetFormat: "mp4",
1982
- estimatedTime: estimateTranscodeTime(formatInfo.duration, "remux")
1983
- };
1984
- }
1985
- return {
1986
- type,
1987
- needsTranscode: true,
1988
- method: "transcode",
1989
- formatInfo,
1990
- targetFormat: "mp4",
1991
- estimatedTime: estimateTranscodeTime(formatInfo.duration, "transcode")
1992
- };
1993
- }
1994
- if (canPlayAudioDirectly(formatInfo)) {
1995
- return {
1996
- type,
1997
- needsTranscode: false,
1998
- method: "direct",
1999
- formatInfo
2000
- };
2001
- }
2002
- if (canRemuxAudio(formatInfo)) {
2003
- return {
2004
- type,
2005
- needsTranscode: true,
2006
- method: "remux",
2007
- formatInfo,
2008
- targetFormat: "m4a",
2009
- estimatedTime: estimateTranscodeTime(formatInfo.duration, "remux")
2010
- };
2011
- }
2012
- return {
2013
- type,
2014
- needsTranscode: true,
2015
- method: "transcode",
2016
- formatInfo,
2017
- targetFormat: "mp3",
2018
- estimatedTime: estimateTranscodeTime(formatInfo.duration, "transcode")
2019
- };
2020
- }
2021
-
2022
- // src/media/transcoder.ts
2023
- import { spawn as spawn2 } from "child_process";
2024
- import fs13 from "fs/promises";
2025
- import path16 from "path";
2026
- import os3 from "os";
2027
- async function getTempOutputPath(sourceFile, targetFormat, tempDir) {
2028
- const dir = tempDir || path16.join(os3.tmpdir(), "file-explorer-media");
2029
- await fs13.mkdir(dir, { recursive: true });
2030
- const baseName = path16.basename(sourceFile, path16.extname(sourceFile));
2031
- const timestamp = Date.now();
2032
- const outputName = `${baseName}_${timestamp}.${targetFormat}`;
2033
- return path16.join(dir, outputName);
2034
- }
2035
- function parseProgress(stderr, duration) {
2036
- const timeMatch = stderr.match(/time=(\d+):(\d+):(\d+)\.(\d+)/);
2037
- if (!timeMatch) return null;
2038
- const hours = parseInt(timeMatch[1]);
2039
- const minutes = parseInt(timeMatch[2]);
2040
- const seconds = parseInt(timeMatch[3]);
2041
- const ms = parseInt(timeMatch[4]);
2042
- const currentTime = hours * 3600 + minutes * 60 + seconds + ms / 100;
2043
- const speedMatch = stderr.match(/speed=\s*([\d.]+)x/);
2044
- const speed = speedMatch ? `${speedMatch[1]}x` : void 0;
2045
- let percent = 0;
2046
- if (duration && duration > 0) {
2047
- percent = Math.min(100, Math.round(currentTime / duration * 100));
2048
- }
2049
- return {
2050
- percent,
2051
- time: currentTime,
2052
- duration,
2053
- speed
2054
- };
2055
- }
2056
- async function remuxVideo(ffmpegPath, inputPath, outputPath, duration, onProgress) {
2057
- return new Promise((resolve, reject) => {
2058
- const args = [
2059
- "-i",
2060
- inputPath,
2061
- "-c",
2062
- "copy",
2063
- // 复制流,不重新编码
2064
- "-movflags",
2065
- "+faststart",
2066
- // 优化 MP4 播放
2067
- "-y",
2068
- // 覆盖输出文件
2069
- outputPath
2070
- ];
2071
- const ffmpeg = spawn2(ffmpegPath, args);
2072
- let stderrBuffer = "";
2073
- ffmpeg.stderr.on("data", (data) => {
2074
- stderrBuffer += data.toString();
2075
- if (onProgress) {
2076
- const progress = parseProgress(stderrBuffer, duration);
2077
- if (progress) {
2078
- onProgress(progress);
2079
- }
2080
- }
2081
- });
2082
- ffmpeg.on("close", (code) => {
2083
- if (code === 0) {
2084
- resolve();
2085
- } else {
2086
- reject(new Error(`ffmpeg exited with code ${code}`));
2087
- }
2088
- });
2089
- ffmpeg.on("error", reject);
2090
- });
2091
- }
2092
- async function transcodeVideo(ffmpegPath, inputPath, outputPath, duration, onProgress) {
2093
- return new Promise((resolve, reject) => {
2094
- const args = [
2095
- "-i",
2096
- inputPath,
2097
- "-c:v",
2098
- "libx264",
2099
- // H.264 编码
2100
- "-preset",
2101
- "fast",
2102
- // 编码速度预设
2103
- "-crf",
2104
- "23",
2105
- // 质量(18-28,越小越好)
2106
- "-c:a",
2107
- "aac",
2108
- // AAC 音频
2109
- "-b:a",
2110
- "192k",
2111
- // 音频比特率
2112
- "-movflags",
2113
- "+faststart",
2114
- "-y",
2115
- outputPath
2116
- ];
2117
- const ffmpeg = spawn2(ffmpegPath, args);
2118
- let stderrBuffer = "";
2119
- ffmpeg.stderr.on("data", (data) => {
2120
- stderrBuffer += data.toString();
2121
- if (onProgress) {
2122
- const progress = parseProgress(stderrBuffer, duration);
2123
- if (progress) {
2124
- onProgress(progress);
2125
- }
2126
- }
2127
- });
2128
- ffmpeg.on("close", (code) => {
2129
- if (code === 0) {
2130
- resolve();
2131
- } else {
2132
- reject(new Error(`ffmpeg exited with code ${code}`));
2133
- }
2134
- });
2135
- ffmpeg.on("error", reject);
2136
- });
2137
- }
2138
- async function remuxAudio(ffmpegPath, inputPath, outputPath, duration, onProgress) {
2139
- return new Promise((resolve, reject) => {
2140
- const args = [
2141
- "-i",
2142
- inputPath,
2143
- "-c",
2144
- "copy",
2145
- "-y",
2146
- outputPath
2147
- ];
2148
- const ffmpeg = spawn2(ffmpegPath, args);
2149
- let stderrBuffer = "";
2150
- ffmpeg.stderr.on("data", (data) => {
2151
- stderrBuffer += data.toString();
2152
- if (onProgress) {
2153
- const progress = parseProgress(stderrBuffer, duration);
2154
- if (progress) {
2155
- onProgress(progress);
2156
- }
2157
- }
2158
- });
2159
- ffmpeg.on("close", (code) => {
2160
- if (code === 0) {
2161
- resolve();
2162
- } else {
2163
- reject(new Error(`ffmpeg exited with code ${code}`));
2164
- }
2165
- });
2166
- ffmpeg.on("error", reject);
2167
- });
2168
- }
2169
- async function transcodeAudio(ffmpegPath, inputPath, outputPath, duration, onProgress) {
2170
- return new Promise((resolve, reject) => {
2171
- const ext = path16.extname(outputPath).toLowerCase();
2172
- const isM4a = ext === ".m4a";
2173
- const args = [
2174
- "-i",
2175
- inputPath,
2176
- "-c:a",
2177
- isM4a ? "aac" : "libmp3lame",
2178
- "-b:a",
2179
- "192k",
2180
- "-y",
2181
- outputPath
2182
- ];
2183
- const ffmpeg = spawn2(ffmpegPath, args);
2184
- let stderrBuffer = "";
2185
- ffmpeg.stderr.on("data", (data) => {
2186
- stderrBuffer += data.toString();
2187
- if (onProgress) {
2188
- const progress = parseProgress(stderrBuffer, duration);
2189
- if (progress) {
2190
- onProgress(progress);
2191
- }
2192
- }
2193
- });
2194
- ffmpeg.on("close", (code) => {
2195
- if (code === 0) {
2196
- resolve();
2197
- } else {
2198
- reject(new Error(`ffmpeg exited with code ${code}`));
2199
- }
2200
- });
2201
- ffmpeg.on("error", reject);
2202
- });
2203
- }
2204
- async function transcodeMedia(ffmpegPath, inputPath, transcodeInfo, tempDir, onProgress) {
2205
- try {
2206
- if (!transcodeInfo.needsTranscode) {
2207
- return {
2208
- success: true,
2209
- outputPath: inputPath
2210
- };
2211
- }
2212
- const targetFormat = transcodeInfo.targetFormat || (transcodeInfo.type === "video" ? "mp4" : "mp3");
2213
- const outputPath = await getTempOutputPath(inputPath, targetFormat, tempDir);
2214
- const duration = transcodeInfo.formatInfo?.duration;
2215
- if (transcodeInfo.type === "video") {
2216
- if (transcodeInfo.method === "remux") {
2217
- await remuxVideo(ffmpegPath, inputPath, outputPath, duration, onProgress);
2218
- } else {
2219
- await transcodeVideo(ffmpegPath, inputPath, outputPath, duration, onProgress);
2220
- }
2221
- } else {
2222
- if (transcodeInfo.method === "remux") {
2223
- await remuxAudio(ffmpegPath, inputPath, outputPath, duration, onProgress);
2224
- } else {
2225
- await transcodeAudio(ffmpegPath, inputPath, outputPath, duration, onProgress);
2226
- }
2227
- }
2228
- if (onProgress) {
2229
- onProgress({ percent: 100, duration });
2230
- }
2231
- return {
2232
- success: true,
2233
- outputPath
2234
- };
2235
- } catch (error) {
2236
- return {
2237
- success: false,
2238
- error: error instanceof Error ? error.message : "Unknown error"
2239
- };
2240
- }
2241
- }
2242
- async function cleanupTranscodedFile(filePath) {
2243
- try {
2244
- if (filePath.includes("file-explorer-media")) {
2245
- await fs13.unlink(filePath);
2246
- }
2247
- } catch {
2248
- }
2249
- }
2250
- async function cleanupAllTranscodedFiles(tempDir) {
2251
- const dir = tempDir || path16.join(os3.tmpdir(), "file-explorer-media");
2252
- try {
2253
- const files = await fs13.readdir(dir);
2254
- await Promise.all(
2255
- files.map((file) => fs13.unlink(path16.join(dir, file)).catch(() => {
2256
- }))
2257
- );
2258
- } catch {
2259
- }
2260
- }
2261
-
2262
- // src/media/service.ts
2263
- import path17 from "path";
2264
- import { execFile as execFile2 } from "child_process";
2265
- import { promisify as promisify4 } from "util";
2266
- var execFileAsync2 = promisify4(execFile2);
2267
- var mediaServiceInstance = null;
2268
- var MediaService = class {
2269
- ffmpegPath;
2270
- ffprobePath;
2271
- tempDir;
2272
- urlEncoder;
2273
- // 缓存转码信息,避免重复检测
2274
- transcodeInfoCache = /* @__PURE__ */ new Map();
2275
- // 缓存已转码文件路径
2276
- transcodedFiles = /* @__PURE__ */ new Map();
2277
- constructor(options) {
2278
- this.ffmpegPath = options.ffmpegPath;
2279
- this.ffprobePath = options.ffprobePath || path17.join(path17.dirname(options.ffmpegPath), "ffprobe");
2280
- this.tempDir = options.tempDir;
2281
- this.urlEncoder = options.urlEncoder;
2282
- }
2283
- /**
2284
- * 检测文件是否需要转码
2285
- */
2286
- async needsTranscode(filePath) {
2287
- const cached = this.transcodeInfoCache.get(filePath);
2288
- if (cached) {
2289
- return cached;
2290
- }
2291
- const info = await detectTranscodeNeeds(filePath, this.ffprobePath);
2292
- this.transcodeInfoCache.set(filePath, info);
2293
- return info;
2294
- }
2295
- /**
2296
- * 执行转码并返回可播放的 URL
2297
- */
2298
- async transcode(filePath, onProgress) {
2299
- const existingOutput = this.transcodedFiles.get(filePath);
2300
- if (existingOutput) {
2301
- return {
2302
- success: true,
2303
- outputPath: existingOutput,
2304
- url: this.urlEncoder ? this.urlEncoder(existingOutput) : `file://${existingOutput}`
2305
- };
2306
- }
2307
- const transcodeInfo = await this.needsTranscode(filePath);
2308
- if (!transcodeInfo.needsTranscode) {
2309
- const url = this.urlEncoder ? this.urlEncoder(filePath) : `file://${filePath}`;
2310
- return {
2311
- success: true,
2312
- outputPath: filePath,
2313
- url
2314
- };
2315
- }
2316
- const result = await transcodeMedia(
2317
- this.ffmpegPath,
2318
- filePath,
2319
- transcodeInfo,
2320
- this.tempDir,
2321
- onProgress
2322
- );
2323
- if (result.success && result.outputPath) {
2324
- this.transcodedFiles.set(filePath, result.outputPath);
2325
- result.url = this.urlEncoder ? this.urlEncoder(result.outputPath) : `file://${result.outputPath}`;
2326
- }
2327
- return result;
2328
- }
2329
- /**
2330
- * 获取媒体元数据
2331
- */
2332
- async getMetadata(filePath) {
2333
- try {
2334
- const { stdout } = await execFileAsync2(this.ffprobePath, [
2335
- "-v",
2336
- "quiet",
2337
- "-print_format",
2338
- "json",
2339
- "-show_format",
2340
- "-show_streams",
2341
- filePath
2342
- ]);
2343
- const data = JSON.parse(stdout);
2344
- const format = data.format || {};
2345
- const tags = format.tags || {};
2346
- const formatInfo = await getMediaFormat(filePath, this.ffprobePath);
2347
- if (!formatInfo) return null;
2348
- return {
2349
- filePath,
2350
- type: formatInfo.type,
2351
- duration: parseFloat(format.duration) || 0,
2352
- format: formatInfo,
2353
- title: tags.title || tags.TITLE,
2354
- artist: tags.artist || tags.ARTIST,
2355
- album: tags.album || tags.ALBUM,
2356
- year: tags.date || tags.DATE || tags.year || tags.YEAR
2357
- };
2358
- } catch {
2359
- return null;
2360
- }
2361
- }
2362
- /**
2363
- * 获取可播放的 URL
2364
- * 如果文件需要转码,则执行转码;否则直接返回文件 URL
2365
- */
2366
- async getPlayableUrl(filePath, onProgress) {
2367
- const result = await this.transcode(filePath, onProgress);
2368
- return result.success ? result.url || null : null;
2369
- }
2370
- /**
2371
- * 清理指定文件的转码缓存
2372
- */
2373
- async cleanupFile(filePath) {
2374
- const transcodedPath = this.transcodedFiles.get(filePath);
2375
- if (transcodedPath) {
2376
- await cleanupTranscodedFile(transcodedPath);
2377
- this.transcodedFiles.delete(filePath);
2378
- }
2379
- this.transcodeInfoCache.delete(filePath);
2380
- }
2381
- /**
2382
- * 清理所有转码缓存
2383
- */
2384
- async cleanup() {
2385
- await cleanupAllTranscodedFiles(this.tempDir);
2386
- this.transcodedFiles.clear();
2387
- this.transcodeInfoCache.clear();
2388
- }
2389
- /**
2390
- * 清除缓存(不删除文件)
2391
- */
2392
- clearCache() {
2393
- this.transcodeInfoCache.clear();
2394
- }
2395
- };
2396
- function initMediaService(options) {
2397
- mediaServiceInstance = new MediaService(options);
2398
- return mediaServiceInstance;
2399
- }
2400
- function getMediaService() {
2401
- return mediaServiceInstance;
2402
- }
2403
- function createMediaService(options) {
2404
- return new MediaService(options);
2405
- }
2406
- export {
2407
- APP_PROTOCOL_HOST,
2408
- APP_PROTOCOL_PREFIX,
2409
- APP_PROTOCOL_SCHEME,
2410
- FileType,
2411
- MediaService,
2412
- SqliteThumbnailDatabase,
2413
- ThumbnailService,
2414
- WatchManager,
2415
- cleanupAllTranscodedFiles,
2416
- cleanupTranscodedFile,
2417
- closeThumbnailDatabase,
2418
- compressFiles,
2419
- copyFiles,
2420
- copyFilesToClipboard,
2421
- createFfmpegVideoProcessor,
2422
- createFile,
2423
- createFolder,
2424
- createMediaService,
2425
- createSharpImageProcessor,
2426
- createSqliteThumbnailDatabase,
2427
- decodeFileUrl,
2428
- deleteFiles,
2429
- detectArchiveFormat,
2430
- detectTranscodeNeeds,
2431
- encodeFileUrl,
2432
- exists,
2433
- extractArchive,
2434
- formatDate,
2435
- formatDateTime,
2436
- formatFileSize,
2437
- getAllSystemPaths,
2438
- getApplicationIcon,
2439
- getClipboardFiles,
2440
- getFileHash,
2441
- getFileInfo,
2442
- getFileType,
2443
- getHomeDirectory,
2444
- getMediaFormat,
2445
- getMediaService,
2446
- getMediaTypeByExtension,
2447
- getPlatform2 as getPlatform,
2448
- getSqliteThumbnailDatabase,
2449
- getSystemPath,
2450
- getThumbnailService,
2451
- getWatchManager,
2452
- initMediaService,
2453
- initThumbnailService,
2454
- isAppProtocolUrl,
2455
- isArchiveFile,
2456
- isDirectory,
2457
- isMediaFile,
2458
- isPreviewable,
2459
- moveFiles,
2460
- openInEditor,
2461
- openInTerminal,
2462
- pasteFiles,
2463
- readDirectory,
2464
- readFileContent,
2465
- readImageAsBase64,
2466
- renameFile,
2467
- revealInFileManager,
2468
- searchFiles,
2469
- searchFilesStream,
2470
- searchFilesSync,
2471
- showFileInfo,
2472
- transcodeMedia,
2473
- watchDirectory,
2474
- writeFileContent
2475
- };
2476
- //# sourceMappingURL=index.js.map
1
+ var t=(t=>(t.FOLDER="folder",t.FILE="file",t.IMAGE="image",t.VIDEO="video",t.MUSIC="music",t.DOCUMENT="document",t.CODE="code",t.TEXT="text",t.ARCHIVE="archive",t.APPLICATION="application",t.UNKNOWN="unknown",t))(t||{}),e="app",r="file",a=`app://${r}`;function n(t){if(!t)return"";const e=t.split("/").map(t=>encodeURIComponent(t)).join("/");return`${a}${e}`}function i(t){if(!t)return"";let e=t;e.startsWith(a)&&(e=e.slice(a.length));const r=e.indexOf("#");-1!==r&&(e=e.substring(0,r));const n=e.indexOf("?");-1!==n&&(e=e.substring(0,n));try{return decodeURIComponent(e)}catch{return e}}function s(t){return t?.startsWith(a)??!1}import o from"path";var c=new Set([".jpg",".jpeg",".png",".gif",".bmp",".webp",".svg",".ico",".tiff",".tif",".heic",".heif"]),u=new Set([".mp4",".mov",".avi",".mkv",".wmv",".flv",".webm",".m4v",".3gp",".mpeg",".mpg"]),l=new Set([".mp3",".wav",".flac",".aac",".ogg",".wma",".m4a",".aiff",".alac"]),m=new Set([".pdf",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".odt",".ods",".odp",".rtf",".pages",".numbers",".key"]),h=new Set([".js",".ts",".jsx",".tsx",".vue",".svelte",".py",".rb",".go",".rs",".java",".kt",".swift",".c",".cpp",".h",".hpp",".html",".css",".scss",".sass",".less",".json",".yaml",".yml",".toml",".xml",".sh",".bash",".zsh",".fish",".ps1",".sql",".graphql",".prisma"]),d=new Set([".txt",".md",".markdown",".log",".ini",".conf",".cfg",".env"]),p=new Set([".zip",".rar",".7z",".tar",".gz",".bz2",".xz",".dmg",".iso"]),f=new Set([".app",".exe",".msi",".deb",".rpm",".pkg",".apk",".ipa"]);function w(t,e){if(e.isDirectory())return t.endsWith(".app")?"application":"folder";const r=o.extname(t).toLowerCase();return c.has(r)?"image":u.has(r)?"video":l.has(r)?"music":m.has(r)?"document":h.has(r)?"code":d.has(r)?"text":p.has(r)?"archive":f.has(r)?"application":"file"}function g(t){return"image"===t||"video"===t||"music"===t}function y(t){return"image"===t||"video"===t||"music"===t||"text"===t||"code"===t}function b(t){if(0===t)return"0 B";const e=Math.floor(Math.log(t)/Math.log(1024));return`${parseFloat((t/Math.pow(1024,e)).toFixed(1))} ${["B","KB","MB","GB","TB"][e]}`}function v(t){const e=(new Date).getTime()-t.getTime(),r=Math.floor(e/864e5);return 0===r?t.toLocaleTimeString("zh-CN",{hour:"2-digit",minute:"2-digit"}):1===r?"昨天":r<7?`${r} 天前`:t.toLocaleDateString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit"})}function E(t){return t.toLocaleString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}import{promises as T}from"fs";import C from"path";var $=t=>`file://${encodeURIComponent(t)}`;async function P(t,e={}){const{urlEncoder:r=$,includeHidden:a=!1,getThumbnailUrl:n}=e;try{const e=await T.readdir(t,{withFileTypes:!0}),i=[];for(const s of e){if(!a&&s.name.startsWith("."))continue;const e=C.join(t,s.name);try{let t;try{t=await T.lstat(e)}catch{try{t=await T.stat(e)}catch(t){t.code;continue}}const a=w(e,t),o=r(e),c={id:e,name:s.name,type:a,dateModified:v(t.mtime),url:o};if(t.isDirectory())c.children=[];else if(c.size=b(t.size),n&&("image"===a||"video"===a)){const t=await n(e);t&&(c.thumbnailUrl=t)}i.push(c)}catch(t){t.code;continue}}return i.sort((t,e)=>"folder"===t.type&&"folder"!==e.type?-1:"folder"!==t.type&&"folder"===e.type?1:t.name.localeCompare(e.name,"zh-CN")),i}catch(t){return[]}}async function S(t){return await T.readFile(t,"utf-8")}async function x(t){try{let e=t;if(t.startsWith("app://file")){const r=t.slice(10);e=decodeURIComponent(r)}else t.startsWith("file://")&&(e=decodeURIComponent(t.replace("file://","")));if(!(await T.stat(e)).isFile())return{success:!1,error:`路径不是文件: ${e}`};const r=(await T.readFile(e)).toString("base64"),a=C.extname(e).toLowerCase().slice(1);return{success:!0,base64:r,mimeType:{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",bmp:"image/bmp",svg:"image/svg+xml"}[a]||"image/jpeg"}}catch(t){return{success:!1,error:String(t)}}}import{promises as F}from"fs";import k from"path";async function D(t,e){try{return await F.writeFile(t,e,"utf-8"),{success:!0}}catch(t){return{success:!1,error:String(t)}}}async function j(t){try{try{await F.access(t)}catch{return await F.mkdir(t,{recursive:!0}),{success:!0,data:{finalPath:t}}}const e=k.dirname(t),r=k.basename(t);let a=2,n=k.join(e,`${r} ${a}`);for(;;)try{await F.access(n),a++,n=k.join(e,`${r} ${a}`)}catch{break}return await F.mkdir(n,{recursive:!0}),{success:!0,data:{finalPath:n}}}catch(t){return{success:!1,error:String(t)}}}async function I(t,e=""){try{const r=k.dirname(t);await F.mkdir(r,{recursive:!0});try{await F.access(t)}catch{return await F.writeFile(t,e,"utf-8"),{success:!0,data:{finalPath:t}}}const a=k.dirname(t),n=k.extname(t),i=k.basename(t,n);let s=2,o=k.join(a,`${i} ${s}${n}`);for(;;)try{await F.access(o),s++,o=k.join(a,`${i} ${s}${n}`)}catch{break}return await F.writeFile(o,e,"utf-8"),{success:!0,data:{finalPath:o}}}catch(t){return{success:!1,error:String(t)}}}import{promises as _}from"fs";async function z(t,e={}){const{adapter:r,useTrash:a=!0,onDeleted:n}=e;try{for(const e of t){if(a&&r?.trashItem)await r.trashItem(e);else{(await _.stat(e)).isDirectory()?await _.rm(e,{recursive:!0,force:!0}):await _.unlink(e)}n?.(e)}return{success:!0,message:a?"文件已移动到回收站":"文件已删除"}}catch(t){return{success:!1,error:String(t)}}}import{promises as N}from"fs";async function M(t,e,r={}){const{onRenamed:a}=r;try{return await N.rename(t,e),a?.(t,e),{success:!0}}catch(t){return{success:!1,error:String(t)}}}import{promises as A}from"fs";import L from"path";async function R(t,e){try{const r=[];for(const a of t){const t=L.basename(a);let n=L.join(e,t),i=1;for(;;)try{await A.access(n);const r=L.extname(t),a=L.basename(t,r);n=L.join(e,`${a} ${++i}${r}`)}catch{break}(await A.stat(a)).isDirectory()?await W(a,n):await A.copyFile(a,n),r.push(n)}return{success:!0,data:{copiedPaths:r}}}catch(t){return{success:!1,error:String(t)}}}async function O(t,e){try{const r=[];for(const a of t){const t=L.basename(a);let n=L.join(e,t),i=1;for(;;)try{await A.access(n);const r=L.extname(t),a=L.basename(t,r);n=L.join(e,`${a} ${++i}${r}`)}catch{break}try{await A.rename(a,n)}catch{(await A.stat(a)).isDirectory()?await W(a,n):await A.copyFile(a,n),await A.rm(a,{recursive:!0,force:!0})}r.push(n)}return{success:!0,data:{movedPaths:r}}}catch(t){return{success:!1,error:String(t)}}}async function W(t,e){await A.mkdir(e,{recursive:!0});const r=await A.readdir(t,{withFileTypes:!0});for(const a of r){const r=L.join(t,a.name),n=L.join(e,a.name);a.isDirectory()?await W(r,n):await A.copyFile(r,n)}}import{promises as U}from"fs";import B from"path";async function X(t){try{const e=await U.stat(t),r=B.basename(t),a=B.extname(r);return{success:!0,data:{path:t,name:r,size:e.size,isFile:e.isFile(),isDirectory:e.isDirectory(),createdAt:e.birthtime,updatedAt:e.mtime,extension:a||void 0}}}catch(t){return{success:!1,error:String(t)}}}async function q(t){try{return await U.access(t),!0}catch{return!1}}async function H(t){try{return(await U.stat(t)).isDirectory()}catch{return!1}}import{exec as V}from"child_process";import{promisify as K}from"util";import*as G from"path";var Y=K(V);function J(){switch(process.platform){case"darwin":return"mac";case"win32":return"windows";default:return"linux"}}async function Q(t){const e=J();try{switch(e){case"mac":{const e=`tell application "Finder"\n activate\n set theFile to POSIX file "${t}" as alias\n open information window of theFile\n end tell`;await Y(`osascript -e '${e}'`);break}case"windows":{const e=t.replace(/'/g,"''"),r=`\n $shell = New-Object -ComObject Shell.Application\n $folder = $shell.Namespace((Split-Path '${e}'))\n $item = $folder.ParseName((Split-Path '${e}' -Leaf))\n $item.InvokeVerb('properties')\n `;await Y(`powershell -Command "${r.replace(/\n/g," ")}"`);break}case"linux":{const e=[`nautilus --select "${t}" && nautilus -q`,`dolphin --select "${t}"`,`thunar --quit && thunar "${G.dirname(t)}"`,`xdg-open "${G.dirname(t)}"`];let r=null;for(const t of e)try{return await Y(t),{success:!0}}catch(t){r=t}throw r||new Error("No file manager found")}}return{success:!0}}catch(t){return{success:!1,error:t instanceof Error?t.message:String(t)}}}async function Z(t){const e=J();try{switch(e){case"mac":await Y(`open -R "${t}"`);break;case"windows":await Y(`explorer.exe /select,"${t}"`);break;case"linux":await Y(`xdg-open "${G.dirname(t)}"`)}return{success:!0}}catch(t){return{success:!1,error:t instanceof Error?t.message:String(t)}}}async function tt(t){const e=J();try{switch(e){case"mac":try{const e=`tell application "iTerm"\n activate\n try\n set newWindow to (create window with default profile)\n tell current session of newWindow\n write text "cd '${t}'"\n end tell\n on error\n tell current window\n create tab with default profile\n tell current session\n write text "cd '${t}'"\n end tell\n end tell\n end try\n end tell`;await Y(`osascript -e '${e}'`)}catch{const e=`tell application "Terminal"\n activate\n do script "cd '${t}'"\n end tell`;await Y(`osascript -e '${e}'`)}break;case"windows":try{await Y(`wt -d "${t}"`)}catch{await Y(`start cmd /K "cd /d ${t}"`)}break;case"linux":{const e=[`gnome-terminal --working-directory="${t}"`,`konsole --workdir "${t}"`,`xfce4-terminal --working-directory="${t}"`,`xterm -e "cd '${t}' && $SHELL"`];let r=null;for(const t of e)try{return await Y(t),{success:!0}}catch(t){r=t}throw r||new Error("No terminal found")}}return{success:!0}}catch(t){return{success:!1,error:t instanceof Error?t.message:String(t)}}}async function et(t){const e=J();try{const r="mac"===e?[`open -a "Cursor" "${t}"`,`open -a "Visual Studio Code" "${t}"`,`code "${t}"`]:[`cursor "${t}"`,`code "${t}"`];let a=null;for(const t of r)try{return await Y(t),{success:!0}}catch(t){a=t}throw a||new Error("No editor found")}catch(t){return{success:!1,error:t instanceof Error?t.message:String(t)}}}import*as rt from"compressing";import*as at from"path";import*as nt from"fs";import{promises as it}from"fs";import{pipeline as st}from"stream/promises";function ot(t){const e=t.toLowerCase();return e.endsWith(".zip")?"zip":e.endsWith(".tar.gz")||e.endsWith(".tgz")?"tgz":e.endsWith(".tar.bz2")||e.endsWith(".tbz2")?"tarbz2":e.endsWith(".tar")?"tar":null}function ct(t){return null!==ot(t)}async function ut(t){const e=[],r=await it.readdir(t,{withFileTypes:!0});for(const a of r){const r=at.join(t,a.name);a.isDirectory()?e.push(...await ut(r)):e.push(r)}return e}async function lt(t,e,r){try{const{format:a,level:n="normal",outputName:i,outputDir:s,deleteSource:o}=e,c=function(t){switch(t){case"zip":default:return".zip";case"tar":return".tar";case"tgz":return".tar.gz";case"tarbz2":return".tar.bz2"}}(a),u=i.endsWith(c)?i:i+c,l=at.join(s,u);await it.mkdir(s,{recursive:!0});const m=await async function(t){let e=0;for(const r of t)(await it.stat(r)).isDirectory()?e+=(await ut(r)).length:e+=1;return e}(t);let h=0;switch(a){case"zip":{const e=new rt.zip.Stream;for(const a of t){const t=await it.stat(a),n=at.basename(a);if(t.isDirectory()){const t=await ut(a);for(const n of t){const t=at.relative(at.dirname(a),n);e.addEntry(n,{relativePath:t}),h++,r?.({currentFile:at.basename(n),processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}}else e.addEntry(a,{relativePath:n}),h++,r?.({currentFile:n,processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}const a=nt.createWriteStream(l);await st(e,a);break}case"tar":{const e=new rt.tar.Stream;for(const a of t){const t=await it.stat(a),n=at.basename(a);if(t.isDirectory()){const t=await ut(a);for(const n of t){const t=at.relative(at.dirname(a),n);e.addEntry(n,{relativePath:t}),h++,r?.({currentFile:at.basename(n),processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}}else e.addEntry(a,{relativePath:n}),h++,r?.({currentFile:n,processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}const a=nt.createWriteStream(l);await st(e,a);break}case"tgz":{const e=new rt.tgz.Stream;for(const a of t){const t=await it.stat(a),n=at.basename(a);if(t.isDirectory()){const t=await ut(a);for(const n of t){const t=at.relative(at.dirname(a),n);e.addEntry(n,{relativePath:t}),h++,r?.({currentFile:at.basename(n),processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}}else e.addEntry(a,{relativePath:n}),h++,r?.({currentFile:n,processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}const a=nt.createWriteStream(l);await st(e,a);break}case"tarbz2":{const e=new rt.tgz.Stream;for(const a of t){const t=await it.stat(a),n=at.basename(a);if(t.isDirectory()){const t=await ut(a);for(const n of t){const t=at.relative(at.dirname(a),n);e.addEntry(n,{relativePath:t}),h++,r?.({currentFile:at.basename(n),processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}}else e.addEntry(a,{relativePath:n}),h++,r?.({currentFile:n,processedCount:h,totalCount:m,percent:Math.round(h/m*100)})}const a=nt.createWriteStream(l.replace(".tar.bz2",".tar.gz"));await st(e,a);break}}if(o)for(const e of t){(await it.stat(e)).isDirectory()?await it.rm(e,{recursive:!0}):await it.unlink(e)}return{success:!0,outputPath:l}}catch(t){return{success:!1,error:t instanceof Error?t.message:String(t)}}}async function mt(t,e,r){try{const{targetDir:a,deleteArchive:n}=e,i=ot(t);if(!i)return{success:!1,error:"不支持的压缩格式"};switch(await it.mkdir(a,{recursive:!0}),r?.({currentFile:at.basename(t),processedCount:0,totalCount:1,percent:0}),i){case"zip":await rt.zip.uncompress(t,a);break;case"tar":await rt.tar.uncompress(t,a);break;case"tgz":await rt.tgz.uncompress(t,a);break;case"tarbz2":return{success:!1,error:"tar.bz2 格式暂不支持"}}return r?.({currentFile:at.basename(t),processedCount:1,totalCount:1,percent:100}),n&&await it.unlink(t),{success:!0,outputPath:a}}catch(t){return{success:!1,error:t instanceof Error?t.message:String(t)}}}import ht from"path";import dt from"os";var pt=process.platform;function ft(t){const e=dt.homedir();switch(t){case"desktop":return ht.join(e,"Desktop");case"documents":return ht.join(e,"Documents");case"downloads":return ht.join(e,"Downloads");case"pictures":return ht.join(e,"Pictures");case"music":return ht.join(e,"Music");case"videos":return"darwin"===pt?ht.join(e,"Movies"):ht.join(e,"Videos");case"applications":return"darwin"===pt?"/Applications":"win32"===pt?process.env.ProgramFiles||"C:\\Program Files":"/usr/share/applications";case"home":return e;case"root":return"darwin"===pt?"/":"win32"===pt?"C:\\":"/";default:return null}}function wt(){const t=["desktop","documents","downloads","pictures","music","videos","applications","home","root"],e={};for(const r of t)e[r]=ft(r);return e}function gt(){return dt.homedir()}function yt(){return process.platform}import{fdir as bt}from"fdir";import vt from"path";import{promises as Et}from"fs";function Tt(t){return new RegExp(t.replace(/\*/g,".*"),"i")}async function Ct(t,e,r){const a=(new bt).withFullPaths().withDirs();r&&r>0&&a.withMaxDepth(r);const n=a.crawl(t),i=await n.withPromise();if(e){const t=Tt(e);return i.filter(e=>t.test(vt.basename(e)))}return i}async function $t(t,e,r,a=100){const n=Tt(e),i=[];let s=!1;await async function t(e){if(!s)try{const o=await Et.readdir(e,{withFileTypes:!0}),c=[],u=[];for(const t of o){if(s)return;const o=vt.join(e,t.name);if(n.test(t.name)&&(c.push(o),i.push(o),i.length>=a))return s=!0,void r(c,!0);t.isDirectory()&&u.push(o)}c.length>0&&r(c,!1);for(const e of u)await t(e)}catch{}}(t),s||r([],!0)}function Pt(t,e,r){const a=(new bt).withFullPaths().withDirs();r&&r>0&&a.withMaxDepth(r);const n=a.crawl(t).sync();if(e){const t=new RegExp(e.replace(/\*/g,".*"),"i");return n.filter(e=>t.test(vt.basename(e)))}return n}import{xxhash64 as St}from"hash-wasm";import{stat as xt}from"fs/promises";async function Ft(t,e){const r=e||await xt(t),a=`${t}:${r.size}:${r.mtime.getTime()}`;return await St(a)}import{promises as kt}from"fs";import Dt from"path";async function jt(t,e){try{const r=[];for(const e of t)try{await kt.access(e),r.push(e)}catch{}return 0===r.length?{success:!1,error:"没有找到有效的文件"}:(e.writeFiles(r),{success:!0})}catch(t){return{success:!1,error:String(t)}}}function It(t){try{const e=t.readFiles();return e&&Array.isArray(e)&&e.length>0?{success:!0,data:{files:e}}:{success:!1,error:"剪贴板中没有文件"}}catch(t){return{success:!1,error:String(t)}}}async function _t(t,e){try{if(!t||"string"!=typeof t)return{success:!1,error:"目标目录路径无效"};if(!e||!Array.isArray(e)||0===e.length)return{success:!1,error:"源文件路径列表无效"};const r=[];for(const a of e){const e=Dt.basename(a);let n=Dt.join(t,e),i=1;for(;;)try{await kt.access(n);const r=Dt.extname(e),a=Dt.basename(e,r);n=Dt.join(t,`${a} ${++i}${r}`)}catch{break}(await kt.stat(a)).isDirectory()?await zt(a,n):await kt.copyFile(a,n),r.push(n)}return{success:!0,data:{pastedPaths:r}}}catch(t){return{success:!1,error:String(t)}}}async function zt(t,e){await kt.mkdir(e,{recursive:!0});const r=await kt.readdir(t,{withFileTypes:!0});for(const a of r){const r=Dt.join(t,a.name),n=Dt.join(e,a.name);a.isDirectory()?await zt(r,n):await kt.copyFile(r,n)}}import{promises as Nt}from"fs";import Mt from"path";import At from"os";import{exec as Lt}from"child_process";import{promisify as Rt}from"util";var Ot=Rt(Lt);async function Wt(t){if("darwin"!==process.platform)return null;try{if(!(await Nt.stat(t)).isDirectory()||!t.endsWith(".app"))return null}catch{return null}const e=await async function(t){const e=Mt.join(t,"Contents","Resources");try{const r=["AppIcon.icns","app.icns","application.icns","icon.icns"],a=Mt.join(t,"Contents","Info.plist");try{const t=(await Nt.readFile(a,"utf-8")).match(/<key>CFBundleIconFile<\/key>\s*<string>([^<]+)<\/string>/);if(t&&t[1]){let r=t[1].trim();r.endsWith(".icns")||(r+=".icns");const a=Mt.join(e,r);try{return await Nt.access(a),a}catch{}}}catch{}for(const t of r){const r=Mt.join(e,t);try{return await Nt.access(r),r}catch{continue}}try{const t=(await Nt.readdir(e)).find(t=>t.toLowerCase().endsWith(".icns"));if(t)return Mt.join(e,t)}catch{}return null}catch{return null}}(t);if(!e)return null;try{const t=Mt.join(At.tmpdir(),`app-icon-${Date.now()}.png`);await Ot(`sips -s format png "${e}" --out "${t}" --resampleHeightWidthMax 128`);const r=await Nt.readFile(t);try{await Nt.unlink(t)}catch{}return`data:image/png;base64,${r.toString("base64")}`}catch{return null}}import*as Ut from"fs";import*as Bt from"path";var Xt=new Map;function qt(t,e){let r=null;try{r=Ut.watch(t,{persistent:!0},(r,a)=>{if(!a)return;const n=Bt.join(t,a),i=`${t}:${a}`,s=Xt.get(i);s&&clearTimeout(s);const o=setTimeout(()=>{Xt.delete(i),Ut.access(n,Ut.constants.F_OK,t=>{let i;i=t?"remove":"rename"===r?"add":"change",e({type:i,path:n,filename:a})})},100);Xt.set(i,o)}),r.on("error",t=>{})}catch(t){}return{close:()=>{r&&(r.close(),r=null);for(const[e,r]of Xt.entries())e.startsWith(`${t}:`)&&(clearTimeout(r),Xt.delete(e))},path:t}}var Ht=class{watchers=new Map;callbacks=new Map;watch(t,e){const r=Bt.normalize(t);let a=this.callbacks.get(r);a||(a=new Set,this.callbacks.set(r,a)),a.add(e);let n=this.watchers.get(r);if(n)n.refCount++;else{n={watcher:qt(r,t=>{const e=this.callbacks.get(r);if(e)for(const r of e)try{r(t)}catch(t){}}),refCount:1},this.watchers.set(r,n)}return()=>{this.unwatch(r,e)}}unwatch(t,e){const r=Bt.normalize(t),a=this.callbacks.get(r);a&&(a.delete(e),0===a.size&&this.callbacks.delete(r));const n=this.watchers.get(r);n&&(n.refCount--,n.refCount<=0&&(n.watcher.close(),this.watchers.delete(r)))}closeAll(){for(const[,t]of this.watchers)t.watcher.close();this.watchers.clear(),this.callbacks.clear()}},Vt=null;function Kt(){return Vt||(Vt=new Ht),Vt}import{promises as Gt}from"fs";import Yt from"path";var Jt=class{database;imageProcessor;videoProcessor;urlEncoder;getApplicationIcon;constructor(t){this.database=t.database,this.imageProcessor=t.imageProcessor||null,this.videoProcessor=t.videoProcessor||null,this.urlEncoder=t.urlEncoder||(t=>`file://${encodeURIComponent(t)}`),this.getApplicationIcon=t.getApplicationIcon||null}async getCachedThumbnailUrl(t){try{const e=await Gt.stat(t),r=w(t,e);if("application"===r&&this.getApplicationIcon)return await this.getApplicationIcon(t);if("image"!==r&&"video"!==r)return null;const a=e.mtime.getTime(),n=this.database.getThumbnailPathFast(t,a);return n?this.urlEncoder(n):(Ft(t,e).then(e=>{this.generateThumbnail(t,e,a).catch(()=>{})}).catch(()=>{}),null)}catch(t){return null}}async getThumbnailUrl(t){try{const e=await Gt.stat(t),r=w(t,e);if("application"===r&&this.getApplicationIcon)return await this.getApplicationIcon(t);if("image"!==r&&"video"!==r)return null;const a=e.mtime.getTime(),n=this.database.getThumbnailPathFast(t,a);if(n)return this.urlEncoder(n);const i=await Ft(t,e),s=await this.generateThumbnail(t,i,a);return s?this.urlEncoder(s):null}catch(t){return null}}async generateThumbnail(t,e,r){const a=this.database.getThumbnailPath(t,e,r);if(a)return a;try{const a=w(t,await Gt.stat(t)),n=`${e.substring(0,16)}.jpg`,i=Yt.join(this.database.getCacheDir(),n);if(function(t,e){return"image"===e}(0,a)&&this.imageProcessor)await this.imageProcessor.resize(t,i,256);else{if(!function(t,e){return"video"===e}(0,a)||!this.videoProcessor)return null;await this.videoProcessor.screenshot(t,i,"00:00:01","256x256")}return this.database.saveThumbnail(t,e,r,i),i}catch(t){return null}}async generateThumbnailsBatch(t,e=3){const r=async t=>{try{await this.generateThumbnail(t.path,t.hash,t.mtime)}catch(t){}};for(let a=0;a<t.length;a+=e){const n=t.slice(a,a+e);await Promise.allSettled(n.map(r))}}deleteThumbnail(t){this.database.deleteThumbnail(t)}cleanupOldThumbnails(){this.database.cleanupOldThumbnails()}},Qt=null;function Zt(t){return Qt=new Jt(t)}function te(){return Qt}import ee from"better-sqlite3";import re from"path";import{existsSync as ae,mkdirSync as ne}from"fs";var ie=class{db=null;cacheDir;dbPath;constructor(t,e={}){const r=e.dirName||"thumbnails",a=e.dbFileName||"thumbnails.db",n=e.dbPath?re.dirname(e.dbPath):null;this.cacheDir=e.thumbnailDir?e.thumbnailDir:n||re.join(t,r),this.dbPath=e.dbPath?e.dbPath:re.join(this.cacheDir,a),ae(this.cacheDir)||ne(this.cacheDir,{recursive:!0})}init(){this.db||(this.db=new ee(this.dbPath,{fileMustExist:!1}),this.db.pragma("journal_mode = WAL"),this.db.exec("\n CREATE TABLE IF NOT EXISTS thumbnails (\n file_path TEXT PRIMARY KEY,\n file_hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n thumbnail_path TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n \n CREATE INDEX IF NOT EXISTS idx_file_hash ON thumbnails(file_hash);\n CREATE INDEX IF NOT EXISTS idx_mtime ON thumbnails(mtime);\n "))}getCacheDir(){return this.cacheDir}getThumbnailPathFast(t,e){this.db||this.init();const r=this.db.prepare("\n SELECT thumbnail_path \n FROM thumbnails \n WHERE file_path = ? AND mtime = ?\n ").get(t,e);return r&&ae(r.thumbnail_path)?r.thumbnail_path:null}getThumbnailPath(t,e,r){this.db||this.init();const a=this.db.prepare("\n SELECT thumbnail_path, mtime \n FROM thumbnails \n WHERE file_path = ? AND file_hash = ?\n ").get(t,e);return a&&a.mtime===r&&ae(a.thumbnail_path)?a.thumbnail_path:null}saveThumbnail(t,e,r,a){this.db||this.init();this.db.prepare("\n INSERT OR REPLACE INTO thumbnails \n (file_path, file_hash, mtime, thumbnail_path, created_at)\n VALUES (?, ?, ?, ?, ?)\n ").run(t,e,r,a,Date.now())}deleteThumbnail(t){this.db||this.init();this.db.prepare("DELETE FROM thumbnails WHERE file_path = ?").run(t)}cleanupOldThumbnails(){this.db||this.init();const t=Date.now()-2592e6;this.db.prepare("DELETE FROM thumbnails WHERE created_at < ?").run(t)}close(){if(this.db){try{this.db.pragma("wal_checkpoint(TRUNCATE)")}catch(t){}this.db.close(),this.db=null}}},se=null;function oe(t){return se||(se=new ie(t.userDataPath,t)).init(),se}function ce(){return se}function ue(){se&&(se.close(),se=null)}import{spawn as le}from"child_process";function me(t){return{async resize(e,r,a){await t(e).resize({width:a,withoutEnlargement:!0}).jpeg({quality:80,optimiseCoding:!0}).toFile(r)}}}function he(t){return{async screenshot(e,r,a,n){const i=n.split("x")[0];return new Promise((n,s)=>{const o=le(t,["-y","-ss",a,"-i",e,"-vframes","1","-vf",`thumbnail,scale=${i}:-1:force_original_aspect_ratio=decrease`,"-c:v","mjpeg","-q:v","6",r]),c=setTimeout(()=>{o.kill(),s(new Error("Video thumbnail generation timeout"))},3e4);o.stderr.on("data",t=>{t.toString().includes("Unsupported pixel format")}),o.on("close",t=>{clearTimeout(c),0===t?n():s(new Error(`ffmpeg exited with code ${t}`))}),o.on("error",t=>{clearTimeout(c),s(t)})})}}}import{execFile as de}from"child_process";import{promisify as pe}from"util";import fe from"path";var we=pe(de),ge=new Set(["mp4","webm","ogg","ogv"]),ye=new Set(["h264","avc1","vp8","vp9","theora","av1"]),be=new Set(["aac","mp3","opus","vorbis","flac"]),ve=new Set(["mp3","wav","ogg","oga","webm","m4a","aac","flac"]),Ee=new Set(["mp3","aac","opus","vorbis","flac","pcm_s16le","pcm_s24le"]),Te=new Set(["h264","avc1","hevc","h265"]),Ce=new Set(["aac","alac"]),$e=new Set(["mp4","mkv","avi","mov","wmv","flv","webm","ogv","ogg","m4v","mpeg","mpg","3gp","ts","mts","m2ts","vob","rmvb","rm"]),Pe=new Set(["mp3","wav","flac","aac","m4a","ogg","oga","wma","ape","alac","aiff","aif","opus","mid","midi","wv","mka"]);function Se(t){const e=fe.extname(t).toLowerCase().slice(1);return $e.has(e)?"video":Pe.has(e)?"audio":null}async function xe(t,e){try{const{stdout:r}=await we(e,["-v","quiet","-print_format","json","-show_format","-show_streams",t]),a=JSON.parse(r),n=a.format||{},i=a.streams||[],s=i.find(t=>"video"===t.codec_type),o=i.find(t=>"audio"===t.codec_type),c=s?"video":"audio",u=n.format_name||"";return{type:c,container:u.split(",")[0].toLowerCase(),videoCodec:s?.codec_name?.toLowerCase(),audioCodec:o?.codec_name?.toLowerCase(),duration:parseFloat(n.duration)||void 0,width:s?.width,height:s?.height,bitrate:parseInt(n.bit_rate)||void 0}}catch{const e=Se(t);if(!e)return null;return{type:e,container:fe.extname(t).toLowerCase().slice(1)}}}function Fe(t,e){if(t&&"direct"!==e)return"remux"===e?Math.ceil(t/50):Math.ceil(t/3)}async function ke(t,e){const r=await xe(t,e);if(!r){return{type:Se(t)||"video",needsTranscode:!1,method:"direct"}}const{type:a}=r;return"video"===a?(n=r,!ge.has(n.container)||n.videoCodec&&!ye.has(n.videoCodec)||n.audioCodec&&!be.has(n.audioCodec)?function(t){if(!t.videoCodec||!Te.has(t.videoCodec))return!1;if(t.audioCodec&&!be.has(t.audioCodec)&&!Ce.has(t.audioCodec))return!1;return!0}(r)?{type:a,needsTranscode:!0,method:"remux",formatInfo:r,targetFormat:"mp4",estimatedTime:Fe(r.duration,"remux")}:{type:a,needsTranscode:!0,method:"transcode",formatInfo:r,targetFormat:"mp4",estimatedTime:Fe(r.duration,"transcode")}:{type:a,needsTranscode:!1,method:"direct",formatInfo:r}):function(t){return!(!ve.has(t.container)||t.audioCodec&&!Ee.has(t.audioCodec))}(r)?{type:a,needsTranscode:!1,method:"direct",formatInfo:r}:function(t){return!!t.audioCodec&&Ce.has(t.audioCodec)}(r)?{type:a,needsTranscode:!0,method:"remux",formatInfo:r,targetFormat:"m4a",estimatedTime:Fe(r.duration,"remux")}:{type:a,needsTranscode:!0,method:"transcode",formatInfo:r,targetFormat:"mp3",estimatedTime:Fe(r.duration,"transcode")};var n}import{spawn as De}from"child_process";import je from"fs/promises";import Ie from"path";import _e from"os";function ze(t,e){const r=t.match(/time=(\d+):(\d+):(\d+)\.(\d+)/);if(!r)return null;const a=3600*parseInt(r[1])+60*parseInt(r[2])+parseInt(r[3])+parseInt(r[4])/100,n=t.match(/speed=\s*([\d.]+)x/),i=n?`${n[1]}x`:void 0;let s=0;return e&&e>0&&(s=Math.min(100,Math.round(a/e*100))),{percent:s,time:a,duration:e,speed:i}}async function Ne(t,e,r,a,n){try{if(!r.needsTranscode)return{success:!0,outputPath:e};const i=r.targetFormat||("video"===r.type?"mp4":"mp3"),s=await async function(t,e,r){const a=r||Ie.join(_e.tmpdir(),"file-explorer-media");await je.mkdir(a,{recursive:!0});const n=`${Ie.basename(t,Ie.extname(t))}_${Date.now()}.${e}`;return Ie.join(a,n)}(e,i,a),o=r.formatInfo?.duration;return"video"===r.type?"remux"===r.method?await async function(t,e,r,a,n){return new Promise((i,s)=>{const o=De(t,["-i",e,"-c","copy","-movflags","+faststart","-y",r]);let c="";o.stderr.on("data",t=>{if(c+=t.toString(),n){const t=ze(c,a);t&&n(t)}}),o.on("close",t=>{0===t?i():s(new Error(`ffmpeg exited with code ${t}`))}),o.on("error",s)})}(t,e,s,o,n):await async function(t,e,r,a,n){return new Promise((i,s)=>{const o=De(t,["-i",e,"-c:v","libx264","-preset","fast","-crf","23","-c:a","aac","-b:a","192k","-movflags","+faststart","-y",r]);let c="";o.stderr.on("data",t=>{if(c+=t.toString(),n){const t=ze(c,a);t&&n(t)}}),o.on("close",t=>{0===t?i():s(new Error(`ffmpeg exited with code ${t}`))}),o.on("error",s)})}(t,e,s,o,n):"remux"===r.method?await async function(t,e,r,a,n){return new Promise((i,s)=>{const o=De(t,["-i",e,"-c","copy","-y",r]);let c="";o.stderr.on("data",t=>{if(c+=t.toString(),n){const t=ze(c,a);t&&n(t)}}),o.on("close",t=>{0===t?i():s(new Error(`ffmpeg exited with code ${t}`))}),o.on("error",s)})}(t,e,s,o,n):await async function(t,e,r,a,n){return new Promise((i,s)=>{const o=Ie.extname(r).toLowerCase(),c=De(t,["-i",e,"-c:a",".m4a"===o?"aac":"libmp3lame","-b:a","192k","-y",r]);let u="";c.stderr.on("data",t=>{if(u+=t.toString(),n){const t=ze(u,a);t&&n(t)}}),c.on("close",t=>{0===t?i():s(new Error(`ffmpeg exited with code ${t}`))}),c.on("error",s)})}(t,e,s,o,n),n&&n({percent:100,duration:o}),{success:!0,outputPath:s}}catch(t){return{success:!1,error:t instanceof Error?t.message:"Unknown error"}}}async function Me(t){try{t.includes("file-explorer-media")&&await je.unlink(t)}catch{}}async function Ae(t){const e=t||Ie.join(_e.tmpdir(),"file-explorer-media");try{const t=await je.readdir(e);await Promise.all(t.map(t=>je.unlink(Ie.join(e,t)).catch(()=>{})))}catch{}}import Le from"path";import{execFile as Re}from"child_process";import{promisify as Oe}from"util";var We=Oe(Re),Ue=null,Be=class{ffmpegPath;ffprobePath;tempDir;urlEncoder;transcodeInfoCache=new Map;transcodedFiles=new Map;constructor(t){this.ffmpegPath=t.ffmpegPath,this.ffprobePath=t.ffprobePath||Le.join(Le.dirname(t.ffmpegPath),"ffprobe"),this.tempDir=t.tempDir,this.urlEncoder=t.urlEncoder}async needsTranscode(t){const e=this.transcodeInfoCache.get(t);if(e)return e;const r=await ke(t,this.ffprobePath);return this.transcodeInfoCache.set(t,r),r}async transcode(t,e){const r=this.transcodedFiles.get(t);if(r)return{success:!0,outputPath:r,url:this.urlEncoder?this.urlEncoder(r):`file://${r}`};const a=await this.needsTranscode(t);if(!a.needsTranscode){return{success:!0,outputPath:t,url:this.urlEncoder?this.urlEncoder(t):`file://${t}`}}const n=await Ne(this.ffmpegPath,t,a,this.tempDir,e);return n.success&&n.outputPath&&(this.transcodedFiles.set(t,n.outputPath),n.url=this.urlEncoder?this.urlEncoder(n.outputPath):`file://${n.outputPath}`),n}async getMetadata(t){try{const{stdout:e}=await We(this.ffprobePath,["-v","quiet","-print_format","json","-show_format","-show_streams",t]),r=JSON.parse(e).format||{},a=r.tags||{},n=await xe(t,this.ffprobePath);return n?{filePath:t,type:n.type,duration:parseFloat(r.duration)||0,format:n,title:a.title||a.TITLE,artist:a.artist||a.ARTIST,album:a.album||a.ALBUM,year:a.date||a.DATE||a.year||a.YEAR}:null}catch{return null}}async getPlayableUrl(t,e){const r=await this.transcode(t,e);return r.success&&r.url||null}async cleanupFile(t){const e=this.transcodedFiles.get(t);e&&(await Me(e),this.transcodedFiles.delete(t)),this.transcodeInfoCache.delete(t)}async cleanup(){await Ae(this.tempDir),this.transcodedFiles.clear(),this.transcodeInfoCache.clear()}clearCache(){this.transcodeInfoCache.clear()}};function Xe(t){return Ue=new Be(t)}function qe(){return Ue}function He(t){return new Be(t)}export{r as APP_PROTOCOL_HOST,a as APP_PROTOCOL_PREFIX,e as APP_PROTOCOL_SCHEME,t as FileType,Be as MediaService,ie as SqliteThumbnailDatabase,Jt as ThumbnailService,Ht as WatchManager,Ae as cleanupAllTranscodedFiles,Me as cleanupTranscodedFile,ue as closeThumbnailDatabase,lt as compressFiles,R as copyFiles,jt as copyFilesToClipboard,he as createFfmpegVideoProcessor,I as createFile,j as createFolder,He as createMediaService,me as createSharpImageProcessor,oe as createSqliteThumbnailDatabase,i as decodeFileUrl,z as deleteFiles,ot as detectArchiveFormat,ke as detectTranscodeNeeds,n as encodeFileUrl,q as exists,mt as extractArchive,v as formatDate,E as formatDateTime,b as formatFileSize,wt as getAllSystemPaths,Wt as getApplicationIcon,It as getClipboardFiles,Ft as getFileHash,X as getFileInfo,w as getFileType,gt as getHomeDirectory,xe as getMediaFormat,qe as getMediaService,Se as getMediaTypeByExtension,yt as getPlatform,ce as getSqliteThumbnailDatabase,ft as getSystemPath,te as getThumbnailService,Kt as getWatchManager,Xe as initMediaService,Zt as initThumbnailService,s as isAppProtocolUrl,ct as isArchiveFile,H as isDirectory,g as isMediaFile,y as isPreviewable,O as moveFiles,et as openInEditor,tt as openInTerminal,_t as pasteFiles,P as readDirectory,S as readFileContent,x as readImageAsBase64,M as renameFile,Z as revealInFileManager,Ct as searchFiles,$t as searchFilesStream,Pt as searchFilesSync,Q as showFileInfo,Ne as transcodeMedia,qt as watchDirectory,D as writeFileContent};