Thank you for reporting this bug. We will make it our priority to review this report.
  1. #1
    Sencha User
    Join Date
    May 2010
    Posts
    14
    Vote Rating
    0
    vosManz is on a distinguished road

      0  

    Default Answered: Question - download file to predefined location

    Answered: Question - download file to predefined location


    Hi,

    I have a question about Sencha Desktop Packager regarding downloading of files.

    I want to build an application where the user can select an online file, edit it on the local computer, and submit/upload it when the file is changed. In the support pages I can find how to open a file, and monitor the file/folder when it changes. Uploading can be done using regular HTML.

    But I can't find the option to download the selected file to a (temporary) location. When I just link to the file, I don't know what the location will be, so that's not an option.

    Is it possible to let my app download a file to a location on the user's computer without the user having to choose the location?

    Thanks in advance.

    Greetings, vosManz

  2. Great question. Currently the way to save a file to disk from a remote URL would be to use XHR, download the binary data, and then save that binary data by passing it in a format compatible with Ion.io.writeFile, like so:

    Code:
    function saveFileToDisk(url, dest, callback) {
        var xhr = new XMLHttpRequest();
    
        xhr.onload = function() {
            var typedData = new Uint8Array(xhr.response),
                ln = typedData.length,
                data = new Array(ln),
                result;
    
            for (var i = 0; i < ln; i++) {
                data[i] = typedData[i];
            }
    
            result = Ion.io.writeFile(dest, data);
            callback(result.success);
        };
    
        xhr.onerror = function() {
            callback(false);
        };
    
        xhr.open('GET', url);
        xhr.responseType = 'arraybuffer';
        xhr.send(null);
    }
    Note that this requires the "allowCrossSite" security manifest setting to be enabled to bypass x-domain security policies. Also, if you are dealing with text files, you can instead just save the xhr.responseText and pass that directly to Ion.io.writeFile as a string, instead of saving it as binary data:

    Code:
    result = Ion.io.writeFile(dest, xhr.responseText);
    This generally is a pain point right now with very large binary files. For files that are < 15-20mb it is generally acceptable. We are working to enhance our file I/O APIs to add in asynchronous streaming and compatibility with the Typed Arrays specification.

    You can listen to click events on those particular file hyperlinks, and handle that click event by saving the file to disk. You could attach a click handler directly on the hyperlinks...

    Code:
    <a href="http://some/url/to/my/file.txt" onclick="saveFileToDisk(this.href, Ion.io.tempPath + 'file.txt', function(){}); return false;">Click Here!</a>
    ...or use the cleaner way and catch all clicks on hyperlinks...

    Code:
    document.body.addEventListener('click', function(e) {
        var t = e.target;
        if (t.tagName == 'A' && t.dataset.fileName) {
            e.preventDefault();
            saveFileToDisk(t.href, Ion.io.tempPath + t.dataset.fileName, function(success) {
                if (success) // do something
                else // do something
            });
        }
    }, true);
    
    <a href="http://some/url/to/my/file.txt" data-fileName="file.txt">Click Here!</a>
    <a href="http://some/url/to/my/file2.txt" data-fileName="file2.txt">Click Here!</a>
    etc.
    Hope that helps.

  3. #2
    Sencha - Desktop Packager Dev Team jarrednicholls's Avatar
    Join Date
    Mar 2007
    Location
    Frederick, MD
    Posts
    1,747
    Vote Rating
    7
    Answers
    20
    jarrednicholls will become famous soon enough jarrednicholls will become famous soon enough

      1  

    Default


    Great question. Currently the way to save a file to disk from a remote URL would be to use XHR, download the binary data, and then save that binary data by passing it in a format compatible with Ion.io.writeFile, like so:

    Code:
    function saveFileToDisk(url, dest, callback) {
        var xhr = new XMLHttpRequest();
    
        xhr.onload = function() {
            var typedData = new Uint8Array(xhr.response),
                ln = typedData.length,
                data = new Array(ln),
                result;
    
            for (var i = 0; i < ln; i++) {
                data[i] = typedData[i];
            }
    
            result = Ion.io.writeFile(dest, data);
            callback(result.success);
        };
    
        xhr.onerror = function() {
            callback(false);
        };
    
        xhr.open('GET', url);
        xhr.responseType = 'arraybuffer';
        xhr.send(null);
    }
    Note that this requires the "allowCrossSite" security manifest setting to be enabled to bypass x-domain security policies. Also, if you are dealing with text files, you can instead just save the xhr.responseText and pass that directly to Ion.io.writeFile as a string, instead of saving it as binary data:

    Code:
    result = Ion.io.writeFile(dest, xhr.responseText);
    This generally is a pain point right now with very large binary files. For files that are < 15-20mb it is generally acceptable. We are working to enhance our file I/O APIs to add in asynchronous streaming and compatibility with the Typed Arrays specification.

    You can listen to click events on those particular file hyperlinks, and handle that click event by saving the file to disk. You could attach a click handler directly on the hyperlinks...

    Code:
    <a href="http://some/url/to/my/file.txt" onclick="saveFileToDisk(this.href, Ion.io.tempPath + 'file.txt', function(){}); return false;">Click Here!</a>
    ...or use the cleaner way and catch all clicks on hyperlinks...

    Code:
    document.body.addEventListener('click', function(e) {
        var t = e.target;
        if (t.tagName == 'A' && t.dataset.fileName) {
            e.preventDefault();
            saveFileToDisk(t.href, Ion.io.tempPath + t.dataset.fileName, function(success) {
                if (success) // do something
                else // do something
            });
        }
    }, true);
    
    <a href="http://some/url/to/my/file.txt" data-fileName="file.txt">Click Here!</a>
    <a href="http://some/url/to/my/file2.txt" data-fileName="file2.txt">Click Here!</a>
    etc.
    Hope that helps.

  4. #3
    Sencha User
    Join Date
    May 2010
    Posts
    14
    Vote Rating
    0
    vosManz is on a distinguished road

      0  

    Default


    Thanks jarrednicholls, works great!

    But now I have a new problem. Should I make a new topic for that? (I will post it here now, if a new topic is needed please let me know

    When writing the file is successful, I open it. Opening works, but when saving the pathChanged function/event is called multiple times.

    So what happens now is:
    - The program opens
    - Temp path is defined and watched, and the pathChanged event is set.
    - When a link is clicked, the function editFile is called with the URL as a parameter
    - When the file is changed, uploadFile is called with path,file,changeType parameters

    My code so far (the base of index.html is copied from the fileBrowser example):
    HTML Code:
    <!DOCTYPE html>
      <html>
      <head>
       <title>File Browser</title>
       <link rel="stylesheet" type="text/css" href="FileBrowser.css" />
       <script type="text/javascript" src="jquery-1.9.1.min.js"></script>
       <script type="text/javascript" src="FileBrowser.js"></script>
       <script type="text/javascript">
        var win = Ion.ui.mainWindow;
        win.setDimensions(300, 300);
        win.center();
        win.show();
       </script>
      </head>
       <body>
        <p><a href="#" onclick="editFile( 'http://localhost/editTest/files/test.txt'); return false;">TXT bestand</a></p>
        <p><a href="#" onclick="editFile( 'http://localhost/editTest/files/test.jpg'); return false;">JPG bestand</a></p>
       </body>
      </html>
    FileBrowser.js:
    Code:
    var boPath = Ion.io.userDesktopPath + 'tmpPath/';
      Ion.io.watchPath( boPath );
      Ion.io.pathChanged.connect( function( path, file, changeType ){
       console.log( path + file + ' - ' + changeType );
       uploadFile( path, file, changeType );
      } );
    
      function editFile( url )
      {
       var xhr = new XMLHttpRequest();
       var fileName = url.split( '/' );
       fileName = fileName[ fileName.length - 1 ];
       xhr.onload = function()
       {
        var typedData = new Uint8Array(xhr.response),
         ln = typedData.length,
         data = new Array(ln),
         result;
        for( var i = 0; i < ln; i++ )
         data[i] = typedData[i];
        result = Ion.io.writeFile( boPath + fileName, data );
        if( result.success )
         Ion.util.openUrl( 'file:' + boPath + fileName );
        else
         console.log( 'error writing file' );
       };
       xhr.onerror = function()
       {
        console.log( 'xhr error' );
       };
       xhr.open( 'GET', url );
       xhr.responseType = 'arraybuffer';
       xhr.send( null );
      }
    
      function uploadFile( path, file, changeType )
      {
       var read = Ion.io.readFile( path + file, { mode: 'binary' } );
       if( read.success )
       {
        console.log( 'read succes' );
        var formdata = new FormData();
        formdata.append( "fileData", read.data );
        formdata.append( "fileName", file );
        if( formdata )
        {
         $.ajax(
         {
          url : "http://localhost/editTest/upload.php",
          type: "POST",
          data: formdata,
          processData: false,
          contentType: false,
          success: function( res )
          {
           console.log( 'upload success' );
          }
         });
        }
       }
      }
    When I run this program and edit a file, all console.log() functions are called multiple times. Also upload.php is called multiple times.
    Maybe I'm using 'Ion.io.pathChanged.connect()' in the wrong way? I couldn't find an example for how to use this event.
    Another thing I notice is that the .exe is twice in my task manager process list. The program window however is only displayed once. Is this normal behavior?

    And my last question;
    As you can see I am now uploading the file using a POST request. I found this example on the internet, but I'd rather handle the file as a 'regular' file upload. Is that possible in javascript? So I can handle the file with $_FILES instead of $_POST/$_GET in php? Maybe anyone has a tip on what's the best way to handle this file upload?

    Thanks in advance for your reply! This is the first time I am using Desktop Packager, so maybe some of the questions are really easy to solve. I want to make a working test before I start building the filebrowser in extJs. Every bit of help is greatly appreciated

    jarrednicholls, thanks again for your download solution!

    Greetings, vosManz
    Last edited by vosManz; 8 Mar 2013 at 2:06 AM. Reason: typo

  5. #4
    Sencha User
    Join Date
    May 2010
    Posts
    14
    Vote Rating
    0
    vosManz is on a distinguished road

      0  

    Default


    Sorry to bump this thread;

    Can anyone check if this behavior (multiple calls of pathChanged event when only 1 file is saved once) is a bug in the Desktop Packager, or am I using the Ion.io.pathChanged event in a wrong way?

    Thanks!

  6. #5
    Sencha User
    Join Date
    May 2010
    Posts
    14
    Vote Rating
    0
    vosManz is on a distinguished road

      0  

    Default


    Hmm, between my 'bump' post and jarrednicholls' post was another post of mine. I edited it, but it seems to be missing now. Or is it not yet approved? Maybe it would be a nice forum feature to see your own posts that are not yet approved, so you know they are not just missing/removed?

  7. #6
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,642
    Vote Rating
    900
    Answers
    3574
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    It was marked for moderation. I have approved it.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  8. #7
    Sencha - Desktop Packager Dev Team jarrednicholls's Avatar
    Join Date
    Mar 2007
    Location
    Frederick, MD
    Posts
    1,747
    Vote Rating
    7
    Answers
    20
    jarrednicholls will become famous soon enough jarrednicholls will become famous soon enough

      1  

    Default


    Quote Originally Posted by vosManz View Post
    Sorry to bump this thread;

    Can anyone check if this behavior (multiple calls of pathChanged event when only 1 file is saved once) is a bug in the Desktop Packager, or am I using the Ion.io.pathChanged event in a wrong way?

    Thanks!
    Hi vosManz, sorry for the delayed response to this particular question.

    pathChanged() hooks up a file system watcher that will propagate OS level events to your callback function. On Windows for example, when you create a new file it will fire an Add event and most likely a Modified event just afterwards when the file stream is flushed and closed. Even individual modifications to files usually results in 2 Modified events being fired because of the same flush/close. OS X and Linux behave a little more predictably, but Windows behaves a little differently with what constitutes a modification event. If I had to sum it up in one word, I would say that Windows is more "sensitive".

    With Desktop Packager, we chose to relay the system level events directly to pathChanged() event handlers. I think in the near future we'll deploy a JavaScript layer that normalizes the chattiness of Windows with the other two OSes. Sencha Architect did this, and very well I might add. There might be something we can do at the file system level as well; we'll investigate.

    So my advice to help manage this will be the same as I offered to the Architect team: Put in place a buffer of time to accept a pathChanged event, and only when that buffer has exceeded would you act upon the last event to come in. Using Ext JS makes this pretty easy, but here's how I would do it in raw JS...

    Code:
    var pathChangedTimeout;
    Ion.io.pathChanged.connect(function() {
        // Save pathChanged arguments to pass over to uploadFile().
        var args = arguments;
    
        // Clear existing timeout ID if any.
        clearTimeout(pathChangedTimeout);
    
        // Wait 100ms before passing the event args to uploadFile.
        // If another pathChanged event fires within 100ms, we ignore this event.
        pathChangedTimeout = setTimeout(function() {
            uploadFile.apply(this, args);
        }, 100);
    });
    Let me know if that makes sense and helps you overcome the chattiness in Windows.

  9. #8
    Sencha User
    Join Date
    May 2010
    Posts
    14
    Vote Rating
    0
    vosManz is on a distinguished road

      0  

    Default


    Hi jarrednicholls,

    Sorry for my late reply. For some reason I didn't get an e-mail notification. Maybe because i'm was not the last poster?

    Thanks again for your answer, works great!

    Brings me to my final question; Uploading the file. What is the best way to upload the file when it was changed?

    Is there a way to upload a file to the server, in the same way as a '<input type="file" />' works?

    In the example in my previous post, I receive an array of the data in PHP. I can loop the array, convert them (with php char() ?) and write it to a file, but I don't think that's a very efficient/safe way to upload a file.

    I also tried uploading using xhr, but that gives the same result. Even when I set the request header to multipart/form-data. I think that the request doesn't see the file as an actual file (makes sense, because it's an object/array :P).

    Does anyone have an idea what's the best solution to solve this?

    Thanks again!

  10. #9
    Sencha - Desktop Packager Dev Team jarrednicholls's Avatar
    Join Date
    Mar 2007
    Location
    Frederick, MD
    Posts
    1,747
    Vote Rating
    7
    Answers
    20
    jarrednicholls will become famous soon enough jarrednicholls will become famous soon enough

      0  

    Default


    This is a good additional question vosManz. As far as I know, yes you can use an <input type="file" /> and simply do a form post. If you want to be a little more fancy (with upload progress events, etc.) you can use FormData and XHR to send the file. I think this stackoverflow Q&A answers it quite well, as well as this MDN article on XHR and using FormData.

    Let me know if you have success

  11. #10
    Sencha User
    Join Date
    May 2010
    Posts
    14
    Vote Rating
    0
    vosManz is on a distinguished road

      0  

    Default


    Hi jarrednicholls,

    The first site you mention is the same site as i found. I tested it, and the request is working, but I can't get the file in the request.

    The problem is that it should work like an input type=file, but it shouldn't be one.. Both examples use it, but that requires the user to select the file. I want this to be automated. As far as i know, it is not possible to set the value of an input type=file, right?

    The 'flow' should be like this:
    1. User opens program.
    2. User clicks a file to edit.
    3. The clicked file is downloaded to a temporary location.
    4. File opens, the user edits and saves it.
    5. When the change is detected, the user is asked to upload it to the server.
    6. When confirmed, the file is uploaded.

    So, at step 6, the changed file should be uploaded automatically. The user doesn't know where the file is (as it is saved in a temporary location), so letting the user select it is not an option.

    Is this possible?

    Thanks again!