@huyooo/file-explorer-core 0.4.5 → 0.4.10

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