zen-fs / core

A filesystem, anywhere
https://zen-fs.github.io/core/
MIT License
103 stars 14 forks source link

Error when creating index: illegal operation on a directory #95

Closed chinonso098 closed 1 month ago

chinonso098 commented 1 month ago

When I run the command npx make-index --output src\osdrive or npx make-index -o src\osdrive , i get the same illegal operation on a directory error.

PS C:\Users\e79217\Downloads\CeetahOS> npx make-index --output src \osdrive
Generated listing for Users/e79217/Dwnloads/CheetahOS
node:fs:2341
    return bindlng.writeFileUtf3( 
                  ^

Error: EISDIR: illegal operation on a directory, open 'C:\Users\e79217\Downoads\CheetahOS\src\osdrive'
    at writeFileSync (node:fs:2341:20)
    at file:///C:/Users/e79217/Downloads/CheetahOS/node_modules/@zenfs/core/scripts/make-index.js:213:1
    at ModuleJob.run (node:internal/modules/esm/module_job:262:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:485:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:109:5)
errno: -4068,
code: 'EISOIR',
syscall: 'open',
path: 'C:\\Users\\e79221\\Downloads\Cheetah06\\src\\osdrive

What could be the cause? And again, thanks for the assist

james-pre commented 1 month ago

@chinonso098 --output/-o expects a file path, not a directory. It is used to customize the index JSON file path.

I've included the output of make-index --help for your reference:

$ npx make-index --help
make-index <path> [...options]

path: The path to create a listing for

options:
        --help, -h              Outputs this help message
        --quiet, -q             The command will not generate any output, including error messages.
        --verbose               Output verbose messages
        --output, -o <path>     Path to the output file. Defaults to listing.
        --ignore, -i <pattern>  Ignores files which match the glob <pattern>. Can be passed multiple times.
chinonso098 commented 1 month ago

Thanks

chinonso098 commented 1 month ago

This is the index.json file created in ZenFS

index.json

This is the osdrive.json file created in BrowserFS

osdrive.json

in BrowserFS, for me to access the contents of the filesystem, I did fs.readdir('/osdrive/Desktop' ......), and it would pull the contents in the Desktop folder.

In ZenFS, I tried fs.readdir('/Desktop' ......) and I get Error: No such file or directory. In ZenFS, I tried fs.readdir('/entries/Desktop' ......) and I get Error: No such file or directory. In ZenFS, I tried fs.readFile('/Desktop/fileexplorer.url' ......) and I get Error: No such file or directory.

james-pre commented 1 month ago

fs.readdir('/Desktop') should work. If it doesn't I can take a deeper look. What does fs.readdirSync('/') give?

chinonso098 commented 1 month ago

Here is what my code looks like

import osDriveFileSystemIndex1 from '../../../index.json';
import ini from 'ini';

import { configure, fs, Overlay, Fetch } from '@zenfs/core';
import { IndexedDB } from '@zenfs/dom';

@Injectable({
    providedIn: 'root',
})
export class FileService {
    private async initZenFSAsync(): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            configure<typeof Overlay>({
                mounts: {
                    '/': {
                        backend: Overlay,
                        readable: { backend: Fetch, index: osDriveFileSystemIndex1 },
                        writable: { backend: IndexedDB, storeName: 'fs-cache' },
                    },
                },
            });
            resolve(console.log('ZenFS is Ready!!!'));
        });
    }
    public async getFilesFromDirectoryAsync1(): Promise<void> {
        // await this.initBrowserFsAsync();
        await this.initZenFSAsync().then(() => {
            fs.readdir('/', (err, files) => {
                if (err) {
                    console.log("Oops! a boo boo happened, filesystem wasn't ready:", err);
                } else {
                    console.log('Here are the files:', files);
                }
            });
        });
    }

    public getFilesFromDirectorySync(): void {
        const result = fs.readdirSync('/');
        console.log(`Here are the files: ${result}`);
    }

    public async getFilesFromDirectoryAsync(dirPath: string): Promise<unknown> {
        await this.initBrowserFsAsync();
        // ...
    }

    // ...
}

For the readdir('/', (err, file)=>{}), this is what I get:

[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.
Angular is running in development mode.
ZenFS is Ready!!!
Here are the files: ► [] 

For the readdirSync('/'), this is what I get:

[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled. 
Angular is running in development mode.
ZenFS is Ready!!! 
► ERROR
Error: Size mismatch: buffer length 2, stats size 4096
    at new PreloadFile (file.js:176:19)
    at StoreFS.openfileSync (fs.js:226:16)
    at _openSync (sync.js:183:19)
    at _readFileSync (sync.js:221:53)
    at Module.readFileSync (sync.js:243:30)
    at file.service.ts:130:28
    at _ZoneDelegate.invoke (zone.js:365:28)
    at Object.onInvoke (core.mjs:15931:33)
    at _ZoneDelegate.invoke (zone.js:364: 34)
    at ZoneImpl.run (zone.js:111:43) 
james-pre commented 1 month ago

I attempted to reproduce the error:

import { configure, fs, Overlay, Fetch } from '@zenfs/core';
import { IndexedDB } from '@zenfs/dom';

await configure({
    mounts: {
        '/': {
            backend: Overlay,
            readable: {
                backend: Fetch,
                index: './index.json',
                baseUrl: 'data',
            },
            writable: { backend: IndexedDB, storeName: 'fs-cache' },
        },
    },
});

console.log('FS configured');
console.log('readdir / (async):', await fs.promises.readdir('/'));
console.log('readdir / (sync):', fs.readdirSync('/'));
console.log('readdir /nested:', await fs.promises.readdir('/nested'));
console.log('readFile /one.txt:', await fs.promises.readFile('/one.txt', 'utf8'));

index.json

Directory structure:

data
├ one.txt
└ nested
    └ two.js

But it gave me the correct and expected output:

FS configured
readdir / (async): ► (2) ['nested', 'one.txt']
readdir / (sync): ► (2) ['nested', 'one.txt']
readdir /nested: ► ['two.js']
readFile /one.txt: OMG!
chinonso098 commented 1 month ago

Hmmmm....... i guess it's something on my end. Let me keep digging

james-pre commented 1 month ago

I recommend you rewrite 2 of the class methods, since they are somewhat difficult to read, and use async/await incorrectly. This may help you address the underlying issue:

export class FileService {
    private async initZenFSAsync(): Promise<void> {
        await configure<typeof Overlay>({
            mounts: {
                '/': {
                    backend: Overlay,
                    readable: { backend: Fetch, index: osDriveFileSystemIndex1 },
                    writable: { backend: IndexedDB, storeName: 'fs-cache' },
                },
            },
        });
        console.log('ZenFS is Ready!!!'));
    }

    public async getFilesFromDirectoryAsync1(): Promise<void> {
        // await this.initBrowserFsAsync();
        await this.initZenFSAsync();
        try {
            console.log('Here are the files:', await fs.promises.readdir('/'));
        } catch (err) {
            console.log("Ooops! a boo boo happened, filesystem wasn't ready:", err);
        }

    }

    // ...
}
chinonso098 commented 1 month ago

Again, thanks a lot for the assist. I will implement the changes and get back to you.

james-pre commented 1 month ago

@chinonso098 What are the entries in the Index?

You can log either fs.mounts.get('/').fs._readable.index, or

const readable = await resolveMountConfig({ backend: Fetch, index: osDriveFileSystemIndex1 });

console.log(readable.index);

await configure<typeof Overlay>({
    mounts: {
        '/': {
            backend: Overlay,
            readable,
            writable: { backend: IndexedDB, storeName: 'fs-cache' },
        },
    },
});

Note you may have to ignore a Typescript error about protected/private members.

chinonso098 commented 1 month ago

here are some of the entries (Document, Games, Pictures, Music, icons ...)

Here is the Index file. index.json

james-pre commented 1 month ago

I know the index.json has the files you mentioned... I'm refering to the runtime Index's entries. Assuming that the Index has all of the correct entries, could you provide me with the value of index.get('/')? This will include the Stats.fileData property, which contains the actual directory entries computed at runtime.

chinonso098 commented 1 month ago

I tried to do what you asked, but i get a 'Type number is not assignable to 1' error

image

chinonso098 commented 1 month ago

Also, for some strange reason, i missed informing you about this error

image

the error points to line 92 of the file.services.ts file. It seems like it is accessing the index file, but it fails to find the data on the server

chinonso098 commented 1 month ago

I get the error : 'Property 'fs' does not exist on type FileSystem' when I try this code fs.mounts.get('/').fs._readble.index

james-pre commented 1 month ago

I tried to do what you asked, but i get a 'Type number is not assignable to 1' error

This is because the imported index's type is not strict enough. You can cast the index object (as IndexJSON), though you are telling Typescript that the version property of the imported object is 1.

the error points to line 92 of the file.services.ts file. It seems like it is accessing the index file, but it fails to find the data on the server

You probably need to set the baseUrl in the Fetch options, since it defaults to using / as the base URL path.

I get the error : 'Property 'fs' does not exist on type FileSystem' when I try this code fs.mounts.get('/').fs._readble.index

You should have an Overlay instance mounted on /. The Overlay instance is a LockedFS wrapping a UnlockedOverlayFS. The UnlockedOverlayFS should have the _readable property set to a FetchFS, which has the index property.

Basically: fs.mounts.get('/')LockedFS .fsUnlockedOverlayFS ._readableFetchFS .indexIndex

chinonso098 commented 1 month ago

what do I set the baseUrl to?

Ok, minor progress. I set baseUrl to baseUrl:'osdrive'

osdrive is the actual folder containing the contents from which the index.json file was created.

The 404 error message is gone, but I still get an empty array for fs.promise.readdir('/')

image

And attempting to the cast the index object to IndexJSON fails

image

Basically: fs.mounts.get('/')LockedFS .fsUnlockedOverlayFS ._readableFetchFS .indexIndex

Ummm.............um....ok

james-pre commented 1 month ago

You need to import IndexJSON, the type won't magically appear. I don't think it is exported by the index.ts, so you will need to import type { IndexJSON } from '@zenfs/core/backends/index/index'

As for the value of baseUrl, it should be the URL to use for the root. For example, if you try to read dirA/file.txt, the request will go to ${baseUrl}/dirA/file.txt, so if your osdrive directory is at localhost:1234/osdrive/, and the current origin is localhost:1234, you can set the baseUrl to osdrive.

chinonso098 commented 1 month ago

I tried to import IndexJSON, but nothing is coming up. Is there some package that I need to install ?

james-pre commented 1 month ago

No, It is defined in src/backends/index/index.ts:12 as IndexData. Sorry for giving you the wrong name for the type.

chinonso098 commented 1 month ago

Thank you, thank you, thank you

We have data 🙂🙂🙂

image

james-pre commented 1 month ago

So was it because of baseUrl?

chinonso098 commented 1 month ago

Correct, it was the baseUrl

chinonso098 commented 1 month ago

I have a number of functions that interact with the fileSystem. At the beginning of each function, will I have to call initZenFSAsync() like the method below

Screenshot 2024-07-31 at 10 48 50 PM

Does the FileSystem know that it is already configured and skips ? because I added a flag to skip needless await configure

Screenshot 2024-07-31 at 10 59 46 PM

If the flag Is not needed. I'll take it out

james-pre commented 1 month ago

No. configure will reconfigure the FS every time you call it. It is intended to be run once, so I recommend you run it once during program initialization. If you have access to top-level await, I strongly encourage you to use it.

If you would like to continue using a flag, A guard clause would be more readable (and decrease indentation):

private async initZenFSAsync(): Promise<void> {
    if(this._isFileSystemInit) {
        return;
    }

    await configure({
        // ...
    });
    this._isFileSystemInit = true;
}

Also @chinonso098 Please stop posting screenshots of your code. It is next to impossible to copy the code, which is making helping you very difficult. As stated in the contributing.md, "Please copy logs, terminal output, and code into a code block in the issue or PR. Do not include screenshots since those are very difficult to debug." So far I've manually been rewriting your screenshots into code blocks but it is making me a little frustrated. Sorry to give you all this, but its important to follow the guidelines on issues.

chinonso098 commented 1 month ago

Will do next time. Again Thanks a lot for the assist. 🙂

james-pre commented 1 month ago

@chinonso098 No problem please let me know if you have more issues. I also encourage you to look at the documentation before opening an issue since many times the problem can be solved without opening an issue (like with baseUrl).