Javascript:按文件路径比较两个选定的文件

时间:2019-03-18 01:07:20

标签: javascript reactjs file blob

我正在寻找一种方法来检查两个文件/文档(PDF,JPG,PNG)是否相同。

如果用户选择一个或多个文件,我会将文件对象转换为javascript对象。我保留了大小,类型,文件名,并创建了一个Blob,以便可以将该对象存储在我的redux存储中。

当用户选择另一个文件时,我想将此文件与已经添加的文件进行比较(因此我可以设置相同的blobURL)。

我可以检查两个文件的名称,类型和大小是否相同,但是所有这些属性都匹配并且文件不相同,因此我想检查文件路径。不幸的是,文件对象中没有提供该属性。是否有办法获得此解决方案或其他解决方案,以确保两个文件都不相同?

1 个答案:

答案 0 :(得分:0)

没有办法获得真实的路径,但这没关系。
您只能访问C:\fakepath\yourfilename.ext形式的FakePath(来自input.value),如果可以访问目录,则有时更多。
但是无论如何,您可能都不希望检查两个文件是否来自硬盘上的同一位置,这一点都不重要,因为自首次访问以来它们很可能已被修改。

但是您可能想做的是检查其内容 是相同的。 为此,您可以比较它们的字节内容:

inp1.onchange = inp2.onchange = e => {
  const file1 = inp1.files[0];
  const file2 = inp2.files[0];
  if(!file1 || !file2) return;
  compare(file1, file2)
    .then(res => console.log('are same ? ', res));
};


function compare(file1, file2) {
  // they don't have the same size, they are different
  if(file1.size !== file2.size)
    return Promise.resolve(false);
  // load both as ArrayBuffers
  return Promise.all([
    readAsArrayBuffer(file1),
    readAsArrayBuffer(file2)
  ]).then(([buf1, buf2]) => {
    // create views over our ArrayBuffers
    const arr1 = new Uint8Array(buf1);
    const arr2 = new Uint8Array(buf2);
    return !arr1.some((val, i) =>
      arr2[i] !== val // search for diffs
    );
  });
}

function readAsArrayBuffer(file) {
  // we could also have used a FileReader,
  // but Response is conveniently already Promise based
  return new Response(file).arrayBuffer();
}
<input type="file" id="inp1">
<input type="file" id="inp2">


现在,您说不再有访问原始文件的权限,并且只能存储可序列化的数据。在这种情况下,一种性能较差的解决方案是从您的文件生成哈希。
多亏了SubtleCrypto API,这可以在前端完成, 但是对于大文件来说,此操作相当慢,您可能希望从服务器系统地执行此操作,而不是在前端执行,并且仅在大小相同时才在前端执行:

// a fake storage object like OP has
const store = [
  { /* an utf-8 text file whose content is `hello world`*/
    name: "helloworld.txt",
    size: 11,
    hash: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" // generated from server
  }
];
// the smae file as the one we fakely stored
const sameFile = new File(['hello world'], 'same-file.txt');
// a file the same size as the one we stored (needs deep check)
const sameSizeButDifferentContent = new File(['random text'], 'differentcontent.txt');


inp.onchange = e => tryToStore(inp.files[0]);

tryToStore(sameFile); // false

tryToStore(sameSizeButDifferentContent); 
// hash: "a4e082f56a58e0855a6abbf2f4ebd08895ff85ea80e634e02b210def84b557dd"


function tryToStore(file) {
  checkShouldStore(file)
    .then(result => {
      console.log('should store', file.name, result)
      if(result)
        store.push(result);
        
      // this is just for demo, in your case you would do it on the server
      if(!result.hash)
        generateHash(file).then(h => result.hash = h);
    });
}

async function checkShouldStore(file) {
  const {name, size} = file;
  const toStore = {name, size, file}; // create a wrapper object
  // first check against the sizes (fast checking)
  const sameSizes = store.filter(obj => obj.size === file.size);
  // only if some files have the same size
  if(sameSizes.length) {
    // then we generate a hash directly
    const hash = await generateHash(file);
    if(sameSizes.some(obj => obj.hash === hash)) {
      return false; // is already in our store
    }
    toStore.hash = hash; // save the hash so we don't have to generate it on server
  }
  return toStore;
}

async function generateHash(file) {
  // read as ArrayBuffer
  const buf = await new Response(file).arrayBuffer();
  // generate SHA-256 hash using crypto API
  const hash_buf = await crypto.subtle.digest("SHA-256", buf);
  // convert to Hex
  const hash_arr = [...new Uint8Array(hash_buf)]
    .map(v => v.toString(16).padStart(2, "0"));
  return hash_arr.join('');
}
<input type="file" id="inp">