Jump to content
petermm

FileWriter

Recommended Posts

What I have up to now: 

Some fixes in in unit “PhoneGapAPI” (check for navigator.device assigned, in PhoneGap.Init)

A static class: 



type
  FileSystem = static class
  private
    class var vFileSystem : JFileSystem;
  public
    class procedure Init;
    Class procedure MyCreateFile(fileSystem: JFileSystem);
    class procedure FileError(error: JFileError);
end;
:
// In procedure TForm1.InitializeObject I call: 
:
  BrowserAPI.document.addEventListener('deviceready', MyPhoneGapInit, false);
:
procedure TForm1.MyPhoneGapInit;
Begin
  If PhoneGap.Ready
    then mmoMessages.Text := mmoMessages.Text + #$d+#$a + 'PhoneGap is already initalized..'
    else Begin
    mmoMessages.Text := mmoMessages.Text + #$d+#$a + 'DEVICE IS READY - Initializing PhoneGap..';
    PhoneGap.Init;
    end;
  BrowserAPI.Document.addEventListener('filePlugInIsReady',ReportfilePlugInIsReady, false);
end;

procedure TForm1.ReportfilePlugInIsReady;
Begin
  asm window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; end;
  mmoMessages.Text := mmoMessages.Text + #$d+#$a + 'FILE PLUGIN IS READY';
  fFSReady := true;
end;


This chain

  InitializeObject -> MyPhoneGapIni  -> ReportFilePlugInIsReady

seems to work so far. What not working is:

  


  BrowserAPI.window.requestFileSystem(0,0,FileSystem.MyCreateFile(NIL),FileSystem.FileError(NIL));

:
Class procedure FileSystem.MyCreateFile(fileSystem: JFileSystem);
Begin
  gStep := 'getFile';
  assert(assigned(fileSystem),'FileSystem.MyCreateFile: filesystem not assigned');
  :

FileSystem.MyCreateFile is called, but the fileSytem Parameter is never assigned.

At least not while testing in the bowser (Chrome BTW). After packing into a Android APK (using the Adobe PhoneGap Webservervice) the Andoid App craches.

Share this post


Link to post
Share on other sites

I not an expert for PhoneGap, but I'm pretty experienced with Cordova (the underlying open source project) and there you need to add the file plugin first. Eventually this could be needed for PhoneGap as well.

Share this post


Link to post
Share on other sites

"..add the file plugin first": As far as I can see, that is done by the PhoneGap build service, at least the log-file says:

This plugin is only applicable for versions of cordova-android greater than 4.0. If you have a previous platform version, you do *not* need this plugin since the whitelist will be built in.
Fetching plugin "cordova-plugin-file" via npm
Installing "cordova-plugin-file" at "4.2.0" for android
Fetching plugin "cordova-plugin-compat" via npm
Installing "cordova-plugin-compat" at "1.0.0" for android

My config.xml file looks like that:

<?xml version="1.0" encoding="UTF-8" ?>
    <widget xmlns   = "http://www.w3.org/ns/widgets"
        xmlns:gap   = "http://phonegap.com/ns/1.0"
        xmlns:android   = "http://schemas.android.com/apk/res/android"  
        id          = "de.sycon.MyROTest"
        versionCode = "10" 
        version     = "0.0.1" >
    <!-- versionCode is optional and Android only -->
    <name>MyROTest</name>
    <description>
        TEST RO and PhoneGapAPI. 
    </description>
    <author  email="pmm@sycon.de">
        Peter MM 
    </author>
  <content src="index.html" />
  <plugin name="cordova-plugin-whitelist" />
  <plugin name="cordova-plugin-file" />
  <feature name="http://api.phonegap.com/1.0/device" />
</widget>

Share this post


Link to post
Share on other sites

Have you tried setting the size parameter to any other value than zero?

 

Something like: 

[...]window.requestFileSystem(0,1024*1024, ...

It might also be worth to compare the PhoneGap header unit (which is already quite old) to the (also quite old and deprecated) unit W3C.FileSystem. The latter is a little bit more verbose and the base for the PhoneGap/Cordova code.

Share this post


Link to post
Share on other sites

OK I'll try that.

But maybe I'm thing to complex: All I need is a "file handle" I can access using "file://...." for a blob (TByteArray) that I already have. 

Since the blob is smaller then 5MB, maybe that can be done without PhoneGap/Cordova at all using wc3.file? But all I see at wc3.file.pas unit is a "FileReader"...

Share this post


Link to post
Share on other sites

The unit W3C.File is a header for the File API (see https://www.w3.org/TR/FileAPI/) which only specifies a writer. In addition to this there is the deprecated unit W3C.FileSystem for the File-System API (https://www.w3.org/TR/file-system-api/). The latter is discontinued and thus deprecated. However you can find previous versions such as this: https://www.w3.org/TR/2012/WD-file-system-api-20120417/. As you can see it doesn't contain a writer either, but this does: https://www.w3.org/TR/2012/WD-file-writer-api-20120417/

 

This said, you can only use this in theory as the API has not been widely implemented so far (and won't ever, based on the working group notes). So it's likely that you need to use Cordova / Phonegap in case you need to write files.

 

If the data you need to store is really little you can also consider to use web storage. I used this solely for all my needs so far. However, for binary data you might need to convert the data to base64 first. Not a big deal, but it might blow the amount of data to a size which is not supported anymore. Also you can't access the data (as files) from outside if needed.

Share this post


Link to post
Share on other sites

... use this in theory.. is definitely not an option but that seems to be the case with PhoneGap as well - at least for me :( 

window.requestFileSystem(0,1024*1024,... 

doesn't make any difference: FileSystem is still unasigned. I've tried in the browser and on androide.

  

As far as I can see, PhoneGap is sharing the Cordova API and what I found for requestFileSystem is:

    /**
     * Request a file system in which to store application data.
     * @param type  local file system type
     * @param size  indicates how much storage space, in bytes, the application expects to need
     * @param successCallback  invoked with a FileSystem object
     * @param errorCallback  invoked if error occurs retrieving file system
     */
    var requestFileSystem = function(type, size, successCallback, errorCallback) {
        argscheck.checkArgs('nnFF', 'requestFileSystem', arguments);
        var fail = function(code) {
            if (errorCallback) {
                errorCallback(new FileError(code));
            }
        };

I'm not a JS hero, that's what I wanted to avoid by using SMS, but that looks OK for me.

What I’m not sure about is if my declaration

Class procedure FileSystem.MyCreateFile(fileSystem: JFileSystem)

and the invocation with:

   BrowserAPI.window.requestFileSystem(0,1024*1024,FileSystem.MyCreateFile(NIL),FileSystem.FileError(NIL));

is OK. Can you confirm, that it should work like that? 

Share this post


Link to post
Share on other sites

I just read it in detail right now. It's not correct how you call it.

 

With:

BrowserAPI.window.requestFileSystem(0,0,FileSystem.MyCreateFile(NIL),FileSystem.FileError(NIL));
you execute the function

FileSystem.MyCreateFile(NIL)
before doing anything else. It is passed with the parameter 'nil', so it's clear why the filesystem is not assigned. If you leave the parameter out it should work.

 

Eventually you might need to add an '@' before the name. So something like:

BrowserAPI.window.requestFileSystem(0, 0, @FileSystem.MyCreateFile, @FileSystem.FileError);
might already do the trick.

Share this post


Link to post
Share on other sites

OK, I wasn't aware of that it is possible to mix in the @something syntax outside asm blocks. (without the @  the codes doesn't compile. As expected, a mssing paramter error is emitted).

I'll have a closer look on it tomorow      .

Share this post


Link to post
Share on other sites

The change to

BrowserAPI.window.requestFileSystem(0, 0, @FileSystem.MyCreateFile, @FileSystem.FileError);

was successfull  :) MyCreateFile is activated now. Next problem is to write data. My code so far:

Class procedure FileSystem.MyCreateFile(fileSystem: JFileSystem);
Var aOptions: Variant;
Begin
  gStep := 'getFile';
  assert(assigned(fileSystem),'FileSystem.MyCreateFile; filesystem not assigned');
  vFileSystem := fileSystem;
  asm
    @aOptions = {create: false, exclusive: false};
  end;
  try
    fileSystem.root.getFile(
      "readme.txt",
      aOptions,
      procedure(parent: JFileEntry)
      Begin
        gStep := 'getWriter';
        parent.CreateWriter(
        Procedure (writer: JFileWriter)
        Var text: String;
        begin
          gStep := 'gotWriter';
          text := 'some text';
          writer.write(text);
          gStep := 'Data writen.';
        end,
        MyFileError
        );
      end,
      MyFileError
      );
  except
    on e: exception do
      LastError := e.message;
  end;
end;

I get the following error:

Uncaught TypeMismatchError: The type of an object was incompatible with the expected type of the parameter associated to the object.

from writer.write(text); According to PhoneGapAPI.pas unit "Var text: String;" should be OK (but what I need finally is to write some binary data...) 

Share this post


Link to post
Share on other sites

First thing I noticed: You can get rid of the asm code section by writing something like the following:

aOptions := class
  create = false;
  exclusive = false;
end;
But that's not a big deal, just might improve the legibility.

 

If the file doesn't exist already you might need to set create to true, though.

 

Next you may consider to remove the surrounding exception handler to defer the exception handling to the underlying browser / cordova. It might have a different (better) hint about the type mismatch. Typically this comes with the position of the error (of the JavaScript code). While it's different to read it might reveal the precise cause of the mismatch.

 

Other than that the code looks OK.

 

At least except for the fact that (at least for Cordova) it would expect binary data instead of a simple text.

 

The Cordova specifications would expect something like:

 

var DataBlob := new JBlob(['some text']);
writer.write(DataBlob);
It might be worth trying this.

If this doesn't work you could try adding the mime type as well. Something like:

var DataBlob := new JBlob(['some text'], class type = 'text/plain' end);
writer.write(DataBlob);
Otherwise you might end with a binary file instead of a pure text file.

Share this post


Link to post
Share on other sites

Txs Christian, 

what I found out and testes successfully was 

 - change to PhoneGapAPI.pas:

      // Writes data to the file with a UTF-8 encoding.
      procedure write(text: String);overload;
      //PMM 7.6.2016: BinaryWriter
      procedure write(BinData: THandle);overload;

- the gotWriter Procedure no looks like that:

        Procedure (writer: JFileWriter)
        Var aBlob: THandle;
        begin
          gStep := 'gotWriter';
          text := 'some text';
          asm
          aBlob = new Blob(["some text"], {type: "text/plain"});
          end;
          writer.write(aBlob);
          gStep := 'Data writen.';
        end,

I'll try if I can avoid the nasty ASM-Blocks by using your code.

 

[Edited] the "aOptions" code compiles OK, but:

var DataBlob := new JBlob(['some text'], class type = 'text/plain' end);

doesn't compile: Syntax Error: Name expected [line: 163, column: 58, file: form1], (Column 58 ist between class and type) 

[/Edited]  

 

 

Question: What is code needed to convert a TByteArray (that is where my real data no is stored in) to a JBlob?

Share this post


Link to post
Share on other sites

The reported error comes from the fact that 'type' is a keyword in Pascal. It has to be escaped. So the correct code would be:
 

var DataBlob := new JBlob(['some text'], class &type = 'text/plain' end);

 

Question: What is code needed to convert a TByteArray (that is where my real data no is stored in) to a JBlob?

 

 

You can create a JBlob directly from a JArrayBuffer, which is defined in W3C.TypedArray. Assuming that TByteArray = array of Byte this would probably translate to

uses
  W3C.TypedArray, W3C.File;

var UInt8Array := JUint8Array.Create(YourByteArray);
var DataBlob := JBlob.Create([UInt8Array]);

where the latter can be used directly by the write procedure.

 

Btw. I would use the following overload instead:

uses
  W3C.File;

[...]

    procedure write(Text: String); overload;
    procedure write(Blob: JBlob); overload;

[...]

The reason: If you want to benefit from the static typing of Object Pascal you should avoid using Variant as much as possible as it does not contain type information. Without these the code suggestions won't work.

 

If you use the JBlob type (from unit W3C.File) you can get access to the fields and methods from this class.

 

Likewise it would make sense to use the JUint8Array to store your binary data as it stores the data in a packed way. In the underlying DWScript (and after compilation in JavaScript) the 'Byte' is otherwise not a Byte but an Integer / Number (stored internally with 64bits). So the size of the byte array will be 8 times bigger than needed. Just saying...

Share this post


Link to post
Share on other sites

OK I'm learning so every hind is welcome  :)

BTW: The THandle / Blob thing was coming form the RTL unit system.io But if there is a type save way to do it 'Lll prefer that, for sure.

 

I've tried your suggestion

var DataBlob := new JBlob(['some text'], class &type = 'text/plain' end);

but that doesn't compile as well:

Syntax Error: There is no overloaded version of "Create" that can be called with these arguments [line: 165, column: 28, file: form1]

 

The TByteArray comes in by the following Code:

Function HexStrToBin(Const aHexStr: AnsiString):TByteArray;
Const
  Convert: array[48..102] {'0'..'f'} of Byte =
    ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
     -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     -1,10,11,12,13,14,15);
var aB: Byte;
  I, aMax: Integer;
Begin
  aMax := Length(aHexStr);
  I := 1;
  While I < aMax
    do Begin
    aB := Convert[Ord(aHexStr[I])] shl 4 + Convert[Ord(aHexStr[I+1])];
    Result.Add(aB);
    I := I + 2;
    end;
end;

Somewhat "delphish" I think. What would be the "smart" way to do it?

My approach is something like that:

  var UInt8Array := JUint8Array.Create(Length(aHexStr) DIV 2);
  I := 1;
  While I < aMax
    do Begin
    aB := Convert[Ord(aHexStr[I])] shl 4 + Convert[Ord(aHexStr[I+1])];
    UInt8Array.Items[I DIV 2] := aB;
    I := I + 2;
    end;

 

Share this post


Link to post
Share on other sites

Eventually, you must cast the class to the right type to make it work. Something like:

var DataBlob := new JBlob(['some text'], JBlobPropertyBag(class &type = 'text/plain' end));

But I have to admit that it looks a bit ugly. The cast means something like: I take care that the code is formaly correct. However a type-safe alternative would be better. Something like:

var BlobProperties: JBlobPropertyBag;
BlobProperties.&type := 'text/plain';
var DataBlob := new JBlob(['some text'], BlobProperties);

But it bloats the code and does not represent the nullable character of JavaScript (where you only need to specify the fields you want). Luckily in this case the latter has no effect because it's just one field here.

 

Regarding the hex string to byte data conversion:

There are many ways to achieve what you want. There's nothing bad in using the Delphi'ish approach. I didn't mean to rewrite the code based on the JUint8Array, but I wanted to mention that storing and handling blobs is better (in terms of memory usage) in this representation.

It can also be useful if you want to mangle the data later...

 

Better alternatives (leaving out the storage here) would be something like:

function HexStrToByteArray(Text: String): array of Integer;
begin
  while Length(Text) >= 2 do
  begin
    Result.Add(HexToInt(Text.Copy(1, 2)));
    Delete(Text, 1, 2);
  end;
end;

The result can then be converted to a JUInt8Array for further storage or processing.

Share this post


Link to post
Share on other sites

Txs Christian but I think we are drifting away to mutch from my original topic "PhoneGap / FileWriter". 

What I have so far is a working example using PG/filewriter :)  (BTW: If you want, I can give that back to the community) 

By "working" I mean it is working from the internal SMS browser, as well as from the browser launched by SMS clicking "open in browser" (that is chrome 50.0.2661.102 here). 

 

Unfortunately it doesn’t work in the following situations :( :

-        Open the same index.html in Chrome started outside SMS - strange

-        Using IE or Safari browser - OK maybe not supported

-        Packed with PhoneGap to an APK and started on an android device (it seems to crash and disappears without a note)

The later one is what is worse for me, because that is what I hopped for…

Any suggestions?

 

Link to the example projekt:

https://www.dropbox.com/s/mk2orrajui4kvnx/TEST_FilePlugIn.zip?dl=0

Share this post


Link to post
Share on other sites

Finally I found, that all the things above works without using phonegap/cordova.

Still don’t know why it runs form inside SMS but not from the chrome browser (even when startet with “--allow-file-access-from-files”) but that seems to be the reasons while it doesn’t work with other browsers/android.

Do make use of phonegap/cordova I guess I have to add something like:

{$R 'file:cordova.js'}
{$R 'file:cordova_plugins.js'}

to my project, but doing so leads to crashes everywhere I’ve tried running it so far. L

Because the FileWriter issue itself seems to be solved, I’ll start another thread under “deployment” to find a solution for the phonegap/cordova issue. Is that OK to do so?

Share this post


Link to post
Share on other sites

Here are some reasons why it might work with some browsers and not with others:

 

The internal browser has the least security restrictions. So if things work there it doesn't mean much. At the same time the internal browser is a bit outdated as the Chromium binaries equals to Chrome 39. This means some deprecated APIs might still be supported there while others (newer) might not be on board yet.

 

When you use open in browser it runs your web-app in your default browser, but still serves the website from within Smart Mobile Studio. This makes a difference as the file access is still handled by Smart Mobile Studio. As opposed to this if you only open the html file in the browser directly (without serving the file) the highest security restrictions apply and thus it might not work at all.

 

The same goes for IE or Safari if the file is not served. If you don't want to (or can't) serve the file with Smart Mobile Studio you can use any other serving tool. My personal favorite for this task is Browsersync.

 

For PhoneGap / Cordova you should consider to install the console plugin. With this you can debug your Cordova application which runs on a different device. Just connect your device via USB, enable USB debugging, eventually start ADB (if not already running) and open the Chrome browser with chrome://inspect.

Share this post


Link to post
Share on other sites

Sorry but what do you mean by "...As opposed to this if you only open the html file in the browser directly (without serving the file) "? What ist the difference between "opening" and "serving" the file? But that is only out of curiosity for my goal is to run it using phonegap/cordova. Didn't expected that to be such a big issue...  

Isn't it possible tor run the result packed by PhoneGap inside my local browser for debugging? The resulting APK is a ZIP file and includes the "www" folder with all plugins needed.

Isn't there any example available, that shows how to make a SMS project "phonegap/cordova aware"?  

 

Created a new task

http://forums.smartmobilestudio.com/index.php?/topic/4107-phonegap-cordova-how-to-use-plugins/

under "deployment" for to discuss the PhoneGap/Cordova issue.   

Share this post


Link to post
Share on other sites

If you only open the file (e.g. by drag & drop the file 'index.html' onto the browser) the file protocol will be used (i.E. 'file:///'). If you serve a file the browser uses the http / https protocol.

 

Regarding PhoneGap / Cordova: It is possible to target the browser as platform. However it can not go beyond the security limits of a browser. So it might be of limited use.

 

In order to use Cordova you have to indeed link to the Cordova API. However, the cordova libraries are typically copied by Cordova itself. The code below should be satisfying to link the Cordova library (if present):

uses
  W3C.HTML5;

// load Cordova library asynchronous
var CordovaScript := JHTMLScriptElement(Document.createElement('script'));
CordovaScript.&type := 'text/javascript';
CordovaScript.async := true;
CordovaScript.src := 'cordova.js';
Document.body.appendChild(CordovaScript);

After you have checked the 'Generate Cordova config.xml' option in the Linker Options of your project you can already easily use Cordova directly.

 

The following commands are needed. Run them in a console in the root directory of your project:

npm i -g cordova

to install Cordova globally.

cordova add platform android

to add the Android platform

cordova build

to build your Cordova app.

 

Eventually you also need to add plugins to the project like this:

cordova plugins add cordova-plugin-file

Share this post


Link to post
Share on other sites

Txs Christian. I wasn't aware of the difference between open and serve in this context.

I'll try to follow your advice on how to link the cordova library and come back on this at the deployment topic.     

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

×