Jump to content

Recommended Posts

  • Moderators

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.

 

Link to post
Share on other sites
  • Moderators

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

Link to post
Share on other sites
  • Moderators

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

Link to post
Share on other sites
  • Moderators

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

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...