vvvfs 0.0.1-alpha.2 → 0.0.1

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/esbuild.config.js CHANGED
@@ -1,13 +1,18 @@
1
- const esbuild = require('esbuild');
1
+ import { build } from 'esbuild';
2
+ import fs from 'fs';
2
3
 
3
- esbuild.build({
4
- entryPoints: ['index.js'],
4
+ const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
5
+
6
+ build({
7
+ entryPoints: ['index.ts'],
5
8
  bundle: true,
6
9
  outfile: 'dist/vvvfs.min.js',
7
- format: 'cjs',
10
+ format: 'iife',
11
+ define: {
12
+ 'process.env.PACKAGE_VERSION': JSON.stringify(pkg.version), // 注入版本号
13
+ },
8
14
  minify: true,
9
15
  platform: 'browser',
10
16
  target: ["chrome80", "firefox70", "safari13", "edge80"],
11
17
  logLevel: 'info',
12
- // external: ['dexie']
13
- });
18
+ }).catch(() => process.exit(1));
package/index.html CHANGED
@@ -4,37 +4,25 @@
4
4
  <head>
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Document</title>
8
- <script src="dist/vvfs.min.js"></script>
7
+ <title>Example</title>
8
+ <script src="dist/vvvfs.min.js"></script>
9
9
  </head>
10
10
 
11
11
  <body>
12
12
  <script>
13
13
  (async () => {
14
- const vvfs = new VVFS();
15
- await vvfs.reset();
16
- console.log(vvfs);
17
- if (!vvfs.exists('/home/root/')) {
18
- console.log('Creating directory /home/root/');
19
- await vvfs.mkdirs('/home/root/');
20
- }
21
- await vvfs.write('/home/root/test.txt', new File(['test content'], 'test.txt'));
22
- await vvfs.write('/home/root/hello.txt', new File(['Hello, World!'], 'hello.txt'));
23
- await vvfs.chmod('/home/root/test.txt', 0o644);
24
- console.log('Changed permissions of /home/root/test.txt to 644');
25
- await vvfs.chmod('/home/root/hello.txt', 0o644);
26
- console.log('Changed permissions of /home/root/hello.txt to 644');
27
- await vvfs.rename('/home/root/test.txt', '/home/root/test-renamed.txt');
28
- console.log('Renamed /home/root/test.txt to /home/root/test-renamed.txt');
29
- const content = await vvfs.read('/home/root/test.txt');
30
- console.log('Content of /home/root/test.txt:', content);
31
- const hello = await vvfs.read('/home/root/hello.txt');
32
- console.log('Content of /home/root/hello.txt:', hello);
33
- // await vvfs.delete('/home/root/test.txt');
34
- // await vvfs.delete('/home/root/hello.txt');
35
- console.log('Deleted /home/root/test.txt and /home/root/hello.txt');
36
- console.log('Listing directory /home/root/:', await vvfs.list('/home/root/'));
37
- console.log('Listing directory /home/:', await vvfs.list('/home/'));
14
+ globalThis.vvvfs = new VVVFS();
15
+ await vvvfs.reset();
16
+ console.log(vvvfs);
17
+ console.log("创建文件", await vvvfs.createFile("/test.txt"));
18
+ console.log("写入文件", await vvvfs.writeText("/test.txt", "Hello World"));
19
+ console.log("文件是否存在", await vvvfs.exists("/test.txt"));
20
+ console.log("文件是否存在", await vvvfs.exists("/test2.txt"));
21
+ console.log("读取文件", await vvvfs.readText("/test.txt"));
22
+ console.log("复制文件", await vvvfs.copy("/test.txt", "/test2.txt"));
23
+ console.log("搜索文件", await vvvfs.search("test"));
24
+ console.log("移动文件", await vvvfs.move("/test2.txt", "/test3.txt"));
25
+ console.log("删除文件", await vvvfs.delete("/test.txt"));
38
26
  })();
39
27
  </script>
40
28
  </body>
package/index.ts ADDED
@@ -0,0 +1,441 @@
1
+ import { Dexie, Table } from "dexie";
2
+ import packageJson from "./package.json" with { type: "json" };
3
+ import mime from "mime";
4
+
5
+ export interface FileRecord {
6
+ id?: number;
7
+ name: string;
8
+ path: string;
9
+ type: string;
10
+ file: File | null;
11
+ }
12
+
13
+ export interface VVVFSDatabase extends Dexie {
14
+ files: Table<FileRecord, number>;
15
+ }
16
+
17
+ const version = packageJson.version;
18
+ class VVVFS {
19
+ private db: VVVFSDatabase;
20
+ options: Record<string, any>;
21
+ /**
22
+ * 虚拟文件系统版本
23
+ */
24
+ static version: string;
25
+ /**
26
+ * 创建虚拟文件系统
27
+ * @param name 虚拟文件系统名称
28
+ * @param options 配置项
29
+ */
30
+ constructor(name = "vvvfs", options = {}) {
31
+ this.options = options;
32
+ this.db = new Dexie(name) as VVVFSDatabase;
33
+ this.db.version(1).stores({
34
+ files: "++id, name, path, type, file",
35
+ });
36
+ }
37
+ /**
38
+ * 重置虚拟文件系统
39
+ */
40
+ async reset() {
41
+ await this.db.delete();
42
+ this.db = new Dexie(this.db.name) as VVVFSDatabase;
43
+ this.db.version(1).stores({
44
+ files: "++id, name, path, type, file",
45
+ });
46
+ }
47
+ /**
48
+ * 创建文件
49
+ * @param path 文件路径
50
+ */
51
+ async createFile(path: string) {
52
+ if (await this.exists(path)) {
53
+ console.warn("文件已存在");
54
+ return true;
55
+ }
56
+ try {
57
+ const { name, parent } = parsePath(path);
58
+ if (!(await this.exists(parent))) {
59
+ await this.createDir(parent);
60
+ }
61
+ console.log(name, parent);
62
+ await this.db.files.add({
63
+ name: name,
64
+ path: parent,
65
+ type: "file",
66
+ file: new File([], name, {
67
+ type: mime.getType(path) || "application/octet-stream",
68
+ }),
69
+ });
70
+ return true;
71
+ } catch (error) {
72
+ console.error("创建文件失败", error);
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * 创建目录
78
+ * @param path 目录路径
79
+ */
80
+ async createDir(path: string) {
81
+ if (await this.exists(path)) {
82
+ console.warn("目录已存在");
83
+ return true;
84
+ }
85
+ try {
86
+ const { name, parent } = parsePath(path);
87
+ if (!(await this.exists(parent))) {
88
+ if (parent == "/") {
89
+ await this.db.files.add({
90
+ name: "",
91
+ path: "/",
92
+ type: "dir",
93
+ file: new File([], ""),
94
+ });
95
+ if (name == "") return true;
96
+ } else {
97
+ await this.createDir(parent);
98
+ }
99
+ }
100
+ console.log(name, parent);
101
+ await this.db.files.add({
102
+ name: name,
103
+ path: parent,
104
+ type: "dir",
105
+ file: new File([], name),
106
+ });
107
+ return true;
108
+ } catch (error) {
109
+ console.error("创建目录失败", error);
110
+ return false;
111
+ }
112
+ }
113
+ /**
114
+ * 判断文件是否存在
115
+ * @param path 文件路径
116
+ */
117
+ async exists(path: string) {
118
+ const { name, parent } = parsePath(path);
119
+ return (
120
+ (await this.db.files
121
+ .where({
122
+ name: name,
123
+ path: parent,
124
+ })
125
+ .count()) > 0
126
+ );
127
+ }
128
+ /**
129
+ * 读取文件内容
130
+ * @param path 文件路径
131
+ */
132
+ async write(path: string, content: Blob) {
133
+ if (!(await this.exists(path))) {
134
+ const success = await this.createFile(path);
135
+ if (!success) {
136
+ console.warn("创建文件失败");
137
+ return false;
138
+ }
139
+ }
140
+ try {
141
+ const { name, parent } = parsePath(path);
142
+ console.log(name, parent);
143
+ if (await this.isDir(parent + "/" + name)) {
144
+ console.warn("路径已存在");
145
+ return false;
146
+ }
147
+ const file = new File([content], name, {
148
+ type: mime.getType(path) || "application/octet-stream",
149
+ });
150
+ console.log(file);
151
+ const fileRecord = await this.db.files.where({ name, path: parent }).first();
152
+ if (fileRecord) {
153
+ await this.db.files.put({
154
+ ...fileRecord,
155
+ file: file,
156
+ });
157
+ return true;
158
+ } else {
159
+ console.warn("文件记录未找到,无法写入内容");
160
+ return false;
161
+ }
162
+ } catch (error) {
163
+ console.error("写入文件失败", error);
164
+ return false;
165
+ }
166
+ }
167
+ /**
168
+ * 写入文本内容
169
+ * @param path 文件路径
170
+ * @param content 文本内容
171
+ */
172
+ async writeText(path: string, content: string) {
173
+ const blob = new Blob([content], { type: "text/plain" });
174
+ return await this.write(path, blob);
175
+ }
176
+ /**
177
+ * 写入JSON内容
178
+ * @param path 文件路径
179
+ * @param content JSON内容
180
+ * @param format 是否格式化
181
+ */
182
+ async writeJson(path: string, content: Record<string, any>, format?: boolean) {
183
+ return await this.writeText(path, JSON.stringify(content, null, format ? 4 : undefined));
184
+ }
185
+ /**
186
+ * 读取文件内容
187
+ * @param path 文件路径
188
+ */
189
+ async read(path: string) {
190
+ if (!(await this.exists(path))) {
191
+ console.warn("文件不存在");
192
+ return null;
193
+ }
194
+ try {
195
+ const { name, parent } = parsePath(path);
196
+ return (await this.db.files.where({ name, path: parent }).first())?.file;
197
+ } catch (error) {
198
+ console.error("读取文件失败", error);
199
+ return null;
200
+ }
201
+ }
202
+ /**
203
+ * 读取文件内容
204
+ * @param path 文件路径
205
+ */
206
+ async readText(path: string) {
207
+ const file = await this.read(path);
208
+ if (file) {
209
+ return await file.text();
210
+ } else {
211
+ return null;
212
+ }
213
+ }
214
+ /**
215
+ * 读取JSON内容
216
+ * @param path 文件路径
217
+ */
218
+ async readJson(path: string) {
219
+ const text = await this.readText(path);
220
+ if (text) {
221
+ try {
222
+ return JSON.parse(text);
223
+ } catch (error) {
224
+ console.error("解析JSON失败", error);
225
+ return null;
226
+ }
227
+ } else {
228
+ return null;
229
+ }
230
+ }
231
+ /**
232
+ * 判断是否是文件
233
+ * @param path 文件路径
234
+ */
235
+ async isFile(path: string) {
236
+ const { name, parent } = parsePath(path);
237
+ return (await this.db.files.where({ name, path: parent, type: "file" }).count()) > 0;
238
+ }
239
+ /**
240
+ * 判断是否是目录
241
+ * @param path 文件路径
242
+ */
243
+ async isDir(path: string) {
244
+ const { name, parent } = parsePath(path);
245
+ return (await this.db.files.where({ path: parent, name, type: "dir" }).count()) > 0;
246
+ }
247
+ /**
248
+ * 列出目录下的文件
249
+ * @param path 目录路径
250
+ */
251
+ async list(path: string) {
252
+ const { name, parent } = parsePath(path);
253
+ return (await this.db.files.where({ path: parent }).toArray()).map((file) => file.name);
254
+ }
255
+ /**
256
+ * 重命名文件
257
+ * @param path 文件路径
258
+ * @param newName 新文件名
259
+ */
260
+ async rename(path: string, newName: string) {
261
+ if (!(await this.exists(path))) throw new Error("File not exists");
262
+ try {
263
+ const { name, parent } = parsePath(path);
264
+ const file = await this.db.files.get({ path, name });
265
+ await this.db.files.put({
266
+ name: newName,
267
+ path: parent,
268
+ type: "file",
269
+ file: file!.file,
270
+ });
271
+ return true;
272
+ } catch (e) {
273
+ console.error(e);
274
+ return false;
275
+ }
276
+ }
277
+ /**
278
+ * 删除文件
279
+ * @param path 文件路径
280
+ */
281
+ async delete(path: string) {
282
+ if (!(await this.exists(path))) return false;
283
+ try {
284
+ const { name, parent } = parsePath(path);
285
+ if (await this.isDir(path)) {
286
+ const files = await this.list(path);
287
+ for (const file of files) {
288
+ if (await this.isDir(joinPath(path, file))) {
289
+ const fileRecord = await this.db.files
290
+ .where({ name, path: parent })
291
+ .first();
292
+ await this.db.files.delete(fileRecord!.id!);
293
+ }
294
+ await this.delete(joinPath(parent, file));
295
+ }
296
+ return true;
297
+ } else {
298
+ const fileRecord = await this.db.files.where({ name, path: parent }).first();
299
+ await this.db.files.delete(fileRecord!.id!);
300
+ return true;
301
+ }
302
+ } catch (e) {
303
+ console.error(e);
304
+ return false;
305
+ }
306
+ }
307
+ /**
308
+ * 移动文件
309
+ * @param path 文件路径
310
+ * @param newPath 新路径
311
+ */
312
+ async move(path: string, newPath: string) {
313
+ if (await this.exists(newPath)) return false;
314
+ if (!(await this.exists(path))) return false;
315
+ try {
316
+ const { name, parent } = parsePath(path);
317
+ const { name: newName, parent: newParent } = parsePath(newPath);
318
+ await this.createDir(newParent);
319
+ if (await this.isDir(path)) {
320
+ const dirs = await this.list(path);
321
+ for (const dir of dirs) {
322
+ await this.move(path + "/" + dir, newPath + "/" + dir);
323
+ }
324
+ } else {
325
+ const fileRecord = await this.db.files.where({ name, path: parent }).first();
326
+ await this.db.files.update(fileRecord!.id!, { name: newName, path: newParent });
327
+ }
328
+ await this.delete(path);
329
+ return true;
330
+ } catch (e) {
331
+ console.error(e);
332
+ return false;
333
+ }
334
+ }
335
+ /**
336
+ * 复制文件
337
+ * @param path 文件路径
338
+ * @param newPath 新路径
339
+ */
340
+ async copy(path: string, newPath: string) {
341
+ if (await this.exists(newPath)) return false;
342
+ if (!(await this.exists(path))) return false;
343
+ try {
344
+ const { name, parent } = parsePath(path);
345
+ const { name: newName, parent: newParent } = parsePath(newPath);
346
+ await this.createDir(newParent);
347
+ if (await this.isDir(path)) {
348
+ const dirs = await this.list(path);
349
+ for (const dir of dirs) {
350
+ await this.move(path + "/" + dir, newPath + "/" + dir);
351
+ }
352
+ } else {
353
+ const fileRecord = await this.db.files.where({ name, path: parent }).first();
354
+ await this.db.files.update(fileRecord!.id!, { name: newName, path: newParent });
355
+ }
356
+ return true;
357
+ } catch (e) {
358
+ console.error(e);
359
+ return false;
360
+ }
361
+ }
362
+ /**
363
+ * 搜索文件
364
+ * @param basePath 基础路径
365
+ * @param query 查询字符串
366
+ */
367
+ async search(basePath: string, query: string) {
368
+ if (!await this.isDir(basePath)) return null;
369
+ const that = this;
370
+ try {
371
+ const dirs = await this.list(basePath);
372
+ return await search(basePath, dirs);
373
+ } catch (e) {
374
+ console.error(e);
375
+ return [];
376
+ }
377
+ async function search(parent: string, files: string[]) {
378
+ const result: string[] = [];
379
+ console.log(files)
380
+ for (const file of files) {
381
+ if (file == "") continue;
382
+ console.log(file);
383
+ if (await that.isDir(joinPath(parent, file))) {
384
+ if (file.includes(query)) {
385
+ result.push(joinPath(parent, file) + "/");
386
+ }
387
+ result.push(...await search(joinPath(parent, file), await that.list(joinPath(parent, file))));
388
+ } else {
389
+ if (file.includes(query)) {
390
+ result.push(joinPath(parent, file));
391
+ }
392
+ }
393
+ }
394
+ return result;
395
+ }
396
+ }
397
+ }
398
+ function parsePath(path: string) {
399
+ path = joinPath(path);
400
+ const oldParts = path.split("/");
401
+ const parts: string[] = [];
402
+ for (let i = 0; i < oldParts.length; i++) {
403
+ if (oldParts[i]) parts.push(oldParts[i]);
404
+ }
405
+ const name = parts.pop() || "";
406
+ const parent = "/" + parts.join("/");
407
+ if ((parent + "/" + name).length > 255) throw new Error("文件名过长");
408
+ return { name, parent };
409
+ }
410
+ function joinPath(...paths: string[]) {
411
+ const segments = paths.map((p) => String(p)).filter((p) => p.length > 0);
412
+ if (segments.length === 0) return ".";
413
+ const isAbsolute = segments[0].startsWith("/");
414
+ const parts = segments.join("/").split("/");
415
+ const stack = [];
416
+ for (const part of parts) {
417
+ if (part === "" || part === ".") {
418
+ continue;
419
+ } else if (part === "..") {
420
+ if (stack.length > 0 && stack[stack.length - 1] !== "..") {
421
+ stack.pop();
422
+ } else if (!isAbsolute) {
423
+ stack.push("..");
424
+ }
425
+ } else {
426
+ stack.push(part);
427
+ }
428
+ }
429
+ let result = stack.join("/");
430
+ if (isAbsolute) {
431
+ result = "/" + result;
432
+ }
433
+ return result || (isAbsolute ? "/" : ".");
434
+ }
435
+ Object.defineProperty(VVVFS, "version", {
436
+ value: packageJson.version,
437
+ writable: false,
438
+ enumerable: true,
439
+ configurable: false,
440
+ });
441
+ (globalThis as any).VVVFS = VVVFS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vvvfs",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1",
4
4
  "description": "一个使用IndexDB实现的虚拟文件系统",
5
5
  "keywords": [
6
6
  "Virtual",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "license": "MIT",
17
17
  "author": "IFTC",
18
- "type": "commonjs",
18
+ "type": "module",
19
19
  "main": "index.js",
20
20
  "scripts": {
21
21
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "dexie": "^4.3.0",
26
- "esbuild": "^0.27.3"
26
+ "esbuild": "^0.27.3",
27
+ "mime": "^4.1.0"
27
28
  }
28
29
  }