Jump to content

Recommended Posts

The original html5 spec had a file api which allowed to read and write files clientside (the File Directories and System Api). This was only fully implemented by Chrome at one point, but even then Chrome needed to be started with a switch (-allow-file-access-from-files).

Today clientside storage is limited to cookies, local/session storage, indexedDB and the Cache API.

While I understand the reasons for limiting file handling on the client, it is sometimes annoying. Fortunately some pretty creative solutions have emerged.

One of them is Filer. This is a nodejs fs filesystem look-a-like which uses indexedDB for storage, and runs in the browser. Basically it allows for a complete filesystem clientside.  Not bad at all.

To continue stacking : Filer is stacked on top of indexedDB. Stack nohost on top of Filer and you have a web fileserver (based on service workers) in the browser. Amazing.

Will certainly try this out.

 

Share this post


Link to post
Share on other sites

This code works (File system in the browser)

  • create and mount a FileSystem
  • create a directory
  • write a file with some contents
  • read the stats of this file
  • read the contents of this file
  • write another file
  • list the directory contents in a memo

Demo

 

procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components

  var Script := browserapi.document.createElement('script');
  Script.src := 'res/filer.js';  //https://raw.githubusercontent.com/filerjs/filer/develop/dist/filer.js
  browserapi.document.head.appendChild(Script);
  Script.onload := procedure
  begin
    writeln('filer loaded');

    var Filer := browserapi.window.Filer;
    writeln('instance created');

    var fs : variant := new JObject;
    asm
      @fs = new (@Filer).FileSystem({
        name: "myfilesystem01",
        flags: [ 'FORMAT' ]
      });
    end;
    writeln('filesystem created');

    //mkdir
    fs.mkdir('/docs', procedure(err:boolean) begin
      if (err) then writeln('Unable to create /docs dir');
    end);

    //write file
    fs.writeFile('/docs/firstDoc.txt', 'Hello World', procedure(err:boolean) begin
      if (err) then writeln('Unable to write /docs/firstDoc.txt');
    end);

    //stats
    fs.stat('/docs/firstDoc.txt', procedure(err, stats:variant) begin
      if (err) then writeln ('Unable to stat /docs/first.txt')
               else writeln(stats);
    end);

    // Read file
    fs.readFile('/docs/firstDoc.txt', 'utf8', procedure (err:boolean; data:string ) begin
      if (err) then writeln ('Unable to read /docs/firstDoc.txt')
               else writeln(data);
    end);

    //write second file
    fs.writeFile('/docs/secondDoc.txt', 'Brave new world', procedure(err:boolean) begin
      if (err) then writeln('Unable to write /docs/secondDoc.txt');
    end);

    //get reference to shell
    var sh : variant := new JObject;
    asm @sh = new (@fs).Shell(); end;

    // list directory, shallow listing
    sh.ls('/docs', procedure(err:boolean; entries: array of variant)
    begin
      if (err)
        then writeln ('Unable to read /docs directory')
        else begin
          W3Memo1.Text := '/docs' + #10;
          for var i := 0 to entries.length -1 do begin
            writeln(entries[i].name);
            W3Memo1.Text := W3Memo1.Text + '  ' + entries[i].name + #10;
          end;
        end;
    end);

  end;
end;

I like this a lot

note : works from file or server, not in the ide

Share this post


Link to post
Share on other sites

A quick test on persistence :

indexedDB (and hence the client file system) is persistent per device. Which means that once a filesystem is created, it will be there the next time the app is activated.

Some caveats though

  • it is pretty safe, but not 100% so. Browsers may delete a database if there is not enough diskspace. Why and when differs between browsers somewhat too.
  • storage access is regulated by 'same origin'. In the demo above the file system is accessible in the lynkfs.com domain, but not in ibm.com  Which is ok.
  • firing up multiple instances of chrome does not disrupt access, the db is accessible in all instances
  • there is no cross-access between browsers (firefox doesn't see indexDB instances created in Chrome)
  • users may clear browser cache/memory/storage

All in all not too bad

Share this post


Link to post
Share on other sites

@lynkfs

It looks like you are performing all of these operations within the scripts OnLoad.

However, in a real-world scenario, I would like to perform reads on application startup and writes on application closing

Would you then have to load the script for each of these (startup, close)

thanx

 

 

Share this post


Link to post
Share on other sites

Scripts need to be loaded once only.

I usually do that in the Form.InitializeForm section, but you can do it anywhere (as long as the script is loaded before use)

procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components

  var Script := browserapi.document.createElement('script');
  Script.src := 'res/filer.js';
  browserapi.document.head.appendChild(Script);
  Script.onload := procedure
  begin
    writeln('script loaded');
    ...

the asynchronous onload event triggers after the script has loaded 

Alternatively you can use the $R directive

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×