使用 HTML5 File API 检查文件是否已更改

IT技术 javascript html
2021-02-12 10:36:05

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

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

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

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

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

3个回答

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

2020 年 8 月更新:下面的替代方案不再有效,并且规范明确禁止它,规定File对象的信息必须反映选择它时基础文件的状态。从规范:

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


你可以使用轮询。记住lastModifiedDateFile,然后当你的查询功能火灾,得到一个新的File实例的输入,看看是否lastModifiedDate已经改变。

这在 Chrome 上对我有用,例如:Live Copy | 来源

(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'>

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

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


本地文件系统

这个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)
}

从拖放中获取条目

与本机文件系统类似,您也可以检索文件句柄并执行相同的操作,但此功能目前适用于所有浏览器。但是这个代码片段在 stackoverflow 中不起作用,因为它使用了一些沙箱,使它不兼容,所以这里是一个几乎没有评论小提琴

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
      }
    }
  }
})
看起来这个 API 目前是默认启用的,感谢您提供此信息!我会仔细看看的。chromestatus.com/feature/6284708426022912
2021-03-16 10:36:05

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

每个 Blob 必须有一个内部快照状态,它必须最初设置为底层存储的状态(如果存在任何此类底层存储),并且必须通过结构化克隆进行保留。可以为文件找到快照状态的进一步规范定义。

选择文件时,输入具有此点的内容的快照。磁盘上的本地更改不会更新快照。

根据我的测试,Chrome 的实现现在符合规范,并且不会更新快照。
2021-03-29 10:36:05
是否有可能只有 Windows 上的 Chrome 违反了规范?Linux 文件系统可能比在 Windows 上更容易实现这一点......
2021-04-05 10:36:05
看起来规范的这部分可能已经放宽了。特别是,对于文件,由于实现困难,它明确地是“应该”而不是“必须”。请参阅规范的“文件接口”部分
2021-04-07 10:36:05
这也是在 macOS 上观察到的行为。
2021-04-14 10:36:05