timarney / react-app-rewired

Override create-react-app webpack configs without ejecting
MIT License
9.81k stars 425 forks source link

unable to read/write files from web worker #504

Closed rjvijesh closed 3 years ago

rjvijesh commented 3 years ago

I am trying to read the text file (log.txt) which has already been created in my local machine and want to append text to the same file from webworker using the filesystem api methods but unable to do so and also unable to get any error logs even though tried sending the errors by postMessage() method to the main thread using the react app wired app

//worker.js

/* eslint no-restricted-globals: 0 */
        // eslint-disable-next-line no-restricted-globals
        self.requestFileSystemSync = self.webkitRequestFileSystemSync || self.requestFileSystemSync;
        // eslint-disable-next-line no-restricted-globals
        self.BlobBuilder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder;
        try {
                var fs = self.requestFileSystemSync(self.TEMPORARY, 1024 * 1024, onFs, onError);
        } catch (e) {
                onError(e);
        }
        function onFs(fs) {
            fs.root.getFile('/Downloads/log.txt', {}, function(fileEntry) {
            // Obtain the File object representing the FileEntry.
            // Use FileReader to read its contents.
            fileEntry.file(function(file) {
            var reader = new FileReader();
                postMessage("file reading process", file);
                reader.onloadend = function(e) {
                postMessage("file reading process result", this.result);
                };
                reader.readAsText(file); // Read the file as plaintext.
            }, onError);
            }, onError);
        }
        //append data to file
            function append(fs, filePath, blob) {
                postMessage("inside append functuin fs ", fs, filePath, blob);
                postMessage("inside append functuin filePath ", filePath);
                postMessage("inside append functuin blob ", blob);
                fs.root.getFile(filePath, {create: false}, function(fileEntry) {
                // Create a FileWriter object for our FileEntry.
                fileEntry.createWriter(function(fileWriter) {
                    fileWriter.seek(fileWriter.length); // Start write position at EOF.
                    fileWriter.write(bb.getBlob('text/plain'));
                }, onError);
            }, onError);
        }
        var bb;
        function onFs(fs) {
            postMessage("onFs function");
            bb = new Blob();
            postMessage("bb ", bb);
            bb.append("helloo world" + '\n');
            postMessage("bb after append", bb);
            append(fs, 'log.txt', bb.getBlob('text/plain'));
            postMessage("file writing", bb);
        }
        function onError(e) {
            postMessage('ERROR: ' + e.toString());
        }
dawnmist commented 3 years ago
  1. The reason you needed the /* eslint no-restricted-globals: 0 */ line at the beginning was you missed marking one of the lines that contained self inside the try:
    try {
     //// This next line contains self too!
    var fs = self.requestFileSystemSync(self.TEMPORARY, 1024 * 1024, onFs, onError);
    } catch (e) {
    onError(e);
    }
  2. You have two functions in the code above: function onFs(fs) {...}. If I understand what you said properly, they're both in the same file...they need to have different names. I'm not sure which of those two functions the self.requestFileSystemSync will be calling - but I'm guessing the answer is "not the one you expected it to call" since you're reporting issues here.

At this point, I believe the issue is actually a problem with your own javascript code. React-app-rewired doesn't do anything itself with webworkers, it just lets you modify how Webpack compiles your files. Webpack's worker loader actually loads/compiles the webworker files. The code within the webworker file is written by yourself. All react-app-rewired does is let you turn on the Webpack worker loader during compilation - any issues in the resulting code belong to you and/or the webpack worker loader.

Your issue is not being caused by react-app-rewired.

rjvijesh commented 3 years ago

Hi Dawnmist,

yes there are two onFs() function on the same file, but i am not using it in the same time, as i am using it one by one by commenting the other onFs() function. the 1st onFs() function is where i am trying to read the existing file on my local machine with absolute path - "c:/Downloads/log.txt" and the 2nd onFs() function is where i am trying to append the data into the already created log.txt file. but both the above code is not working as i have also tried to returning the result or error message to the main thread also.

just wanted to know the path which i am giving is it correct? as i have tried both way "/Downloads/log.txt" and "log.txt" but still i am getting compilation error

image

dawnmist commented 3 years ago

I have not previously used the FileSystemApi, so I have looked up some links to help: https://www.html5rocks.com/en/tutorials/file/filesystem-sync/ https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry/file https://developer.mozilla.org/en-US/docs/Web/API/File_and_Directory_Entries_API https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystemSync https://developer.mozilla.org/en-US/docs/Web/API/FileSystem https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryEntry/getFile

There's a lot more info on google, but that should get you at least started.

From what I can see, the synchronous versions of the filesystem api for requestFileSystemSync does not take a callback and error function, so you're using the function incorrectly. This means that your onFs and onError functions will never be called by requestFileSystemSync because it only uses the first 2 parameters you are giving it.

In order to use the file system from the request, you literally just use the fs variable created by requestFileSystemSync directly. When the access is synchronous, the next line of the code doesn't start running until the previous line completes.

i.e.

  try {
    var fs = self.requestFileSystemSync(self.TEMPORARY, 1024 * 1024);
    var fileEntry = fs.root.getFile('Downloads/log.txt', {});
    fileEntry.file(function(file) {
      let reader = new FileReader();

      reader.onload = function() {
        postMessage("file reading process result", reader.result);
      };

      reader.onerror = function() {
        onError(reader.error);
      }

      reader.readAsText(file);
    }, onError);
  } catch (error) {
    onError(error);
  }

Your problems are errors in your webworker code. Please read up on the filesystem api, as yes you are misusing it.

rjvijesh commented 3 years ago

Hi Dawnmist,

have tried the below code

try { var fs = self.requestFileSystemSync(self.TEMPORARY, 1024 * 1024); var fileEntry = fs.root.getFile('Downloads/log.txt', {}); fileEntry.file(function(file) { let reader = new FileReader();

  reader.onload = function() {
    postMessage("file reading process result", reader.result);
  };

  reader.onerror = function() {
    onError(reader.error);
  }

  reader.readAsText(file);
}, onError);

} catch (error) { onError(error); }

but still i am getting the below error. TypeError: Failed to execute 'getFile' on 'DirectoryEntrySync': cannot convert to dictionary.

Need to know the root path where it is searching for the file exists check as the log.txt file is present in the c:/users/Downloads/log.txt path

dawnmist commented 3 years ago

The root path is the C:/ directory.

rjvijesh commented 3 years ago

tried adding the path "/Downloads/log.txt" but still it is not able to read the existing file from the c:/ drive location.

Also wanted to check with you, whether the root path works with other OS like linux or any other OS, or we need to use any other api methods for read/write files

dawnmist commented 3 years ago

What exactly is the whole path to your file?

My understanding is that if it is in C:/users/<username>/Downloads/log.txt, you would need the path "users//Downloads/log.txt".

The path "/Downloads/log.txt" would map to "C:/Downloads/log.txt".

Basically, take the full path without the beginning drive letter. Everything after that very first / character is required.

How about doing some simple message logging of what the root path contains using the createReader function as documented here. Simply send the retrieved list of FileSystemEntry name properties through to your main thread as a message and log them to the console. The properties you can access are defined at https://developer.mozilla.org/en-US/docs/Web/API/FileSystemEntry - the name, isFile, isDirectory in particular are ones you could put into an array to send to the main thread in a json message.

Combining the example from the first link with the message to send:

function readDirectory(directory) {
  let dirReader = directory.createReader();
  let entries = [];

  let getEntries = function() {
    dirReader.readEntries(function(results) {
      if (results.length) {
        // only grabbing the fields to report in the main thread's console log in the array to return
        entries = entries.concat(toArray(results)
                         .map((entry) => ({
                           name: entry.name,
                           isFile: entry.isFile,
                           isDirectory: entry.isDirectory
                         }));
        getEntries();
      }
    }, function(error) {
      /* handle error -- error is a FileError object */
    });
  };

  getEntries();
  return entries;
}

Used:

const postFileList = (directory, filenames) => {
  postMessage("FILE_LIST", JSON.stringify({ dirname: directory.name, filenames }));
};

const rootDirFiles = readDirectory(fs.root);
postFileList(fs.root, rootDirFiles);

You can then examine exactly what you get for various paths, starting from root then moving deeper. i.e. Once you know the directories in root, start trying to get the file list in their subdirectory:

const userDir = fs.root.getDirectory('Users');
const userDirFiles = readDirectory(userDir);
postFileList(userDir, userDirFiles);

const usernameDir = fs.root.getDirectory('Users/<username>');
const usernameDirFiles = readDirectory(usernameDir);
postFileList(usernameDir, usernameDirFiles);

and so on, sending the list of files. Having them all have the same "key" string for the message means that on the main thread when you catch the message you only need to define how to handle the file list message once (based on the "FILE_LIST" string) and then you know that the data has the directory name itself and the list of file/directory names inside it.

Note: above code is currently untested, so there may be typos (e.g. missing brackets).

You should be able to debug yourself what the correct paths to use are using the above steps once you have the output from the fs.root directory. Just step along the path one directory at a time - if the first step doesn't work, you're calling the directory incorrectly in some way - don't move onto the second step along the path until you have that first step working (e.g. get "Users" working before trying "Users/<username>". The first directory step MUST BE one of the directory names that you logged for the root directory.

There should be no difference between Windows and Linux for the api in general, with the possible exception that on Windows you may need to use \\ instead of / as a directory separator. Note also that in Linux paths are case-sensitive and I don't know if this api follows the case-sensitivity (e.g. "users" and "Users" are not the same path), so I'd recommend typing the path exactly as it is displayed in the file list you get back from reading the root directory list. I haven't seen the directory separators or case-sensitivity documented as required for this api, so I think it should simply work with the / if you actually give it the full path to the file.

Please also note that this issue is completely unrelated to react-app-rewired in any way. Google has a ton of information available if you search for how to use the functions above. That's where I'm finding the answers I'm providing here.