Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Input file size and content do not update on macOS

I wrote a small web based tool, which uses a file input to read a constantly changing file. The user selects it manually (once!) and JavaScript tracks when it was changed (last file modification time and file size). If it has changed, it reads the file contents again.

This works fine in all browsers on Windows. But on macOS (tested in Safari 10.1.2 and Firefox 51.0.1) only the last modification time seems to be updated. The file size is not updated and it seems, that the file contents cannot be read anymore too. So I can not track file changes in browsers on macOS.

But why? Is this a security limitation in macOS?

Please test with following snippet. Select a file (for example a text file), see last modified timestamp and file size, then change file and look again, if size has changed. On macOS the file size doesn't change.

No jQuery please.

window.addEventListener('load', function() {
  window.setInterval(function() {
    var logFile = document.querySelector('#file').files[0];
    if (logFile) {
      document.querySelector('#info').innerHTML = '<br/>' +
        (new Date()).toString() + '<br/>Last modified: ' +
        logFile.lastModified +
        '<br/>Size: ' +
        logFile.size;
    }
  }, 1000);
});
#info {
  font-family: Courier;
  font-size: 0.9em;
}
<!DOCTYPE html>
<input type="file" id="file" />
<p id="info"></p>
like image 446
StanE Avatar asked May 16 '18 12:05

StanE


2 Answers

I don't think it is a security measure rather than simply a performance measure.
Once they've got the file metadata info, they won't request it anymore on further getting => less access to user's disk.
To be honest, I am even a bit surprised that on Windows they do request these metadata every time.

To workaround this, is not an easy task, and I would reconsider the needs for this, and if the messaging can't be done at another level (e.g from th process that does modify the files).

Indeed, both Firefox and Safari don't behave the same in this situation:

  • Safari seems to create a blobURI pointing to the File as soon as you've picked it form the input, and always uses it when you try to access it later (e.g from a FileReader). The real problem here is that we can't get the new version on disk, because, for whatever reasons, this blobURI doens't seem to be a real pointer to the disk... But if we only want to detect file changes, then it's good for us, since we would only have to check for a FileReader.onerror event, which would work even with a File.slice(0,1) (i.e minimal I/O).

  • Firefox on the other hand will trigger the FileReader's error only if the size of the cached metadata doesn't match with the one that has been read. This means that you would have to read the whole File at every check, and, if no error occured, double check that the data are actually the same... But, in this browser, you could still fetch the actual File on disk from AJAX using a blobURI, in order to get the latest version.

like image 198
Kaiido Avatar answered Nov 09 '22 13:11

Kaiido


I remember i had a problem reading the file input again and again before. apparently it flagged the file as modified and i had problem using the FileReader on the file after it was modified. but there is way around it


There is a way in that you can use to read the content of a folder. Once you have the directory entery from a drag and drop or a folder selection from a directory-file input you can loop over each file again and again to see if something has changed.

for some reason it didn't work in SO due to sandboxing restriction so i created a jsfiddle also

function traverseFileTree(entery, path) {
  path = path || ""

  if (entery.isFile) {
    // Get file
    entery.file(file => {
        setInterval(() => {
          entery.file(file => {
            console.log(file.lastModifiedDate, file.size, path)
          })
        }, 1000)
    })
  } else if (entery.isDirectory) {
    // Get folder contents
    var dirReader = entery.createReader()
    dirReader.readEntries(entries => {
      for (let entery of entries) {
        traverseFileTree(entery, path + entery.name + '/')
      }
    }, console.error)
  }
}

var dropzone = document.getElementById('dropzone')

dropzone.addEventListener("drop", function(event) {
  event.preventDefault()

  const items = event.dataTransfer.items

  for (item of items) {
    // webkitGetAsEntry is where the magic happens
    const entery = item.webkitGetAsEntry()

    if (entery) {
      traverseFileTree(entery)
    }
  }
}, false)

// Required for drop event to event do what we want
dropzone.ondragover = event => {
  event.preventDefault()
  return false
}
#dropzone{
  background: black;
  height: 30px;
  color: white;
  padding: 10px;
}
<div id="dropzone">
  drop a directory here
</div>
like image 37
Endless Avatar answered Nov 09 '22 14:11

Endless