【问题标题】:Check if file has changed using HTML5 File API使用 HTML5 File API 检查文件是否已更改
【发布时间】:2012-12-26 09:17:20
【问题描述】:

好的,所以我有一个程序可以将一些特定数据输出到制表符分隔的变量文件中。

我一直在使用 Excel 打开和查看文件内容,但是我发现 excel 坚持锁定它打开的每个文件非常烦人,因为如果我在 Excel 中打开文件,我的程序会崩溃......但我会真的很喜欢在每次运行程序后整齐地更新数据,所以我不必一直关闭并重新打开文件。

所以,我决定使用 Javascript 来解析文件并将其显示在 html 表格中是最简单的,而且确实如此。我很快就把东西撞到了一起。现在,如果我将文件保留在显示状态,我的程序不会崩溃,但是,它仍然没有更新……而且我每次都必须打开新生成的文件。

所以,我想知道是否有一种机制可以让我的 Javascript 以某种方式通知另一个进程对文件的更改?我知道这不太可能,但我想避免出于明显的原因简单地轮询文件。

我对 JS 很熟悉,但是 HTML5 和新的 API 对我来说都是新的。

【问题讨论】:

    标签: javascript html


    【解决方案1】:

    我不相信File API 有任何文件更改事件,只有进度事件等。

    2020 年 8 月更新:下面的替代方案不再有效,规范 specifically disallows it 指出 File 对象的信息必须反映基础文件在被选中时的状态。来自规范:

    用户代理应努力将文件对象的快照状态设置为获取引用时磁盘上底层存储的状态。如果在进行引用之后文件在磁盘上被修改,则文件的快照状态将不同于底层存储的状态。


    你可以使用轮询。记住FilelastModifiedDate,然后当你的轮询函数触发时,获取一个新的File 实例作为输入,看看lastModifiedDate 是否发生了变化。

    这适用于我在 Chrome 上,例如:Live Copy | Source

    (function() {
      var input;
      var lastMod;
    
      document.getElementById('btnStart').onclick = function() {
        startWatching();
      };
        function startWatching() {
            var file;
    
            if (typeof window.FileReader !== 'function') {
                display("The file API isn't supported on this browser yet.");
                return;
            }
    
            input = document.getElementById('filename');
            if (!input) {
                display("Um, couldn't find the filename element.");
            }
            else if (!input.files) {
                display("This browser doesn't seem to support the `files` property of file inputs.");
            }
            else if (!input.files[0]) {
                display("Please select a file before clicking 'Show Size'");
            }
            else {
                file = input.files[0];
          lastMod = file.lastModifiedDate;
                display("Last modified date: " + lastMod);
          display("Change the file");
          setInterval(tick, 250);
            }
        }
    
      function tick() {
        var file = input.files && input.files[0];
        if (file && lastMod && file.lastModifiedDate.getTime() !== lastMod.getTime()) {
          lastMod = file.lastModifiedDate;
                display("File changed: " + lastMod);
        }
      }
    
      function display(msg) {
        var p = document.createElement('p');
        p.innerHTML = msg;
        document.body.appendChild(p);
      }
    })();
    <input type='file' id='filename'>
    <input type='button' id='btnStart' value='Start'>

    【讨论】:

    • 我已经通过在其他工作中寻找时间来实现这一点。我现在只是在测试它。不过看起来应该很棒。谢谢!
    • Firefox 似乎缓存了文件,它每次都会产生相同的日期。
    • @TomášZato:嗯,很遗憾我可以在 Firefox 上复制你的结果。 :-|字符“cach”(例如,“cache”、“caching”等)根本没有出现在规范中,所以我想它可以做它想做的事。可以在 File 上使用“刷新”方法或类似的方法。
    • 这不再适用于任何主流浏览器(根据规范也不应该)。在此处查看评论:stackoverflow.com/questions/62246532/…
    • 谢谢@leo!已验证,找到规范的相关部分,并更新了答案。
    【解决方案2】:

    这个问题有两种解决方案,&lt;input type="file"&gt; 不是其中之一。根据规范,它会创建文件的“快照”。


    本机文件系统

    这个api 是实验性的,需要在闪烁时启用标志(又名铬浏览器)。这个想法是你得到一个文件句柄,当你需要文件时,你调用异步“getFile”函数来检索实际文件。

    此功能是一项“强大功能”,需要您的网站是安全的,并且不能在沙盒 iframe 中使用。

    所以这里没有测试是一些“黑暗中的代码”:

    // triggerd on click
    async function pickFile () {
      const handle = showOpenFilePicker()
      let lastModificationTime = 0
      
      async function compare () {
        const file = await handle.getFile()
        if (file.lastModified > lastModificationTime) {
          lastModificationTime = +file.lastModified
          console.log(await file.text())
        }
      }
      
      setInterval(compare, 1000)
    }
    

    通过拖放获取条目

    与本机文件系统类似,您也可以检索文件句柄并执行相同的操作,但此功能适用于当今的所有浏览器。但是这段代码 sn-p 在 stackoverflow 中不起作用,因为它使用了一些沙盒功能使其不兼容,所以这里有一个 fiddle 有几个 cmets

    function drop(event) {
      event.stopPropagation();
      event.preventDefault();
        
      // get the file as an fileEntry (aka file handle)
      const fileEntry = event.dataTransfer.items[0].webkitGetAsEntry()
      let lastModificationTime = 0
      
      async function read (file) {
        // use the new async read method on blobs.
        console.log(await file.text())
      }
      
      function compare (meta) {
        if (meta.modificationTime > lastModificationTime) {
          lastModificationTime = meta.modificationTime
          fileEntry.file(read)
        }
      }
      
      setInterval(fileEntry.getMetadata.bind(fileEntry, compare), 1000)
    }
    

    编辑:现在还有一种方法可以将拖放文件作为 FileSystemFileHandle 更好地使用

    elem.addEventListener('dragover', evt => {
      // Prevent navigation.
      evt.preventDefault()
    })
    elem.addEventListener('drop', async evt => {
      // Prevent navigation.
      evt.preventDefault()
    
      // Process all of the items.
      for (const item of evt.dataTransfer.items) {
        // kind will be 'file' for file/directory entries.
        if (item.kind === 'file') {
          const entry = await item.getAsFileSystemHandle();
          if (entry.kind === 'file') {
            // use same solution as the first Native File System solution
          }
        }
      }
    })
    

    【讨论】:

    【解决方案3】:

    虽然 T.J. Crowder 的回答是正确的,Chrome 的实现似乎违反了规范。

    每个 Blob 都必须有一个内部快照状态,如果存在任何此类底层存储,则必须将其初始设置为底层存储的状态,并且必须通过结构化克隆进行保存。对于文件,可以找到更多关于快照状态的规范定义。

    当一个文件被选中时,输入会有一个当时内容的快照。磁盘上的本地更改不会更新快照。

    【讨论】:

    • Windows 上的 Chrome 是否有可能违反规范? Linux 文件系统可能比在 Windows 上更容易实现这一点...
    • 这也是在 macOS 上观察到的行为。
    • 看起来这部分规范可能已经放宽了。特别是对于文件,由于实施困难,它明确地是“应该”而不是“必须”。 See the "File Interface" part of the spec
    • 根据我的测试,Chrome 的实现现在符合规范,并且不会更新快照。
    猜你喜欢
    • 1970-01-01
    • 2018-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-17
    • 1970-01-01
    相关资源
    最近更新 更多