Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hash a directory #18

Open
rrthomas opened this issue Apr 9, 2021 · 5 comments
Open

Hash a directory #18

rrthomas opened this issue Apr 9, 2021 · 5 comments

Comments

@rrthomas
Copy link

rrthomas commented Apr 9, 2021

It would be great to be able to use this module to do the equivalent of ipfs add -r --only-hash.

@rrthomas
Copy link
Author

rrthomas commented Apr 9, 2021

In fact, this should be very easy to achieve, as if I change:

for await (const { cid } of importer([{ content }], block, options)) {

to

for await (const { cid } of importer(content, block, options)) {

then I can pass in the result of e.g. globsource().

So it seems that all that is needed to keep things as convenient as at the moment is a bit more automatic type-matching code like the current:

  if (typeof content === 'string') {
    content = new TextEncoder().encode(content)
  }

@rrthomas
Copy link
Author

rrthomas commented Apr 9, 2021

After a bit more thought, I can see how to detect whether the argument is a Uint8Array, but not how to distinguish the case where it's an AsyncIterable<Uint8Array> (where we want to lift it into an array containing an object) from an AsyncIterable<ImportCandidate> (where we want to leave it alone).

If it's impossible to make this distinction, then it would be possible instead to add a second API to ipfs-only-hash.

@rrthomas
Copy link
Author

rrthomas commented Oct 4, 2021

For now, I am using the following code, and specifically the ofDir method:

// A slightly modified version of ipfs-only-hash
// See https://github.com/alanshaw/ipfs-only-hash/issues/18
const { globSource } = require('ipfs-http-client');
const { importer } = require('ipfs-unixfs-importer');

const block = {
  get: async (cid) => { throw new Error(`unexpected block API get for ${cid}`); },
  put: async () => { throw new Error('unexpected block API put'); },
};

async function hash(content_, options_) {
  const options = options_ || {};
  options.onlyHash = true;

  let content = content_;
  if (typeof content === 'string') {
    content = [{ content: new TextEncoder().encode(content) }];
  } else if (content instanceof Object.getPrototypeOf(Uint8Array)) {
    content = [{ content }];
  }

  let lastCID;
  for await (const { cid } of importer(content, block, options)) {
    lastCID = cid;
  }
  return lastCID;
}

module.exports = {
  cidToHex(cid) {
    return `0x${Buffer.from(cid.bytes.slice(2)).toString('hex')}`;
  },

  of: hash,

  async ofDir(directory) {
    const options = {
      cidVersion: 0, // Lines up with the smart contract code
    };

    const files = globSource(directory, { recursive: true });
    const rootCID = await hash(files, options);
    return rootCID;
  },
};

@wilfredjonathanjames
Copy link

wilfredjonathanjames commented Nov 14, 2022

Updated version of @rrthomas's code. Note ofFile is distinct to ofDir, as it doesn't wrap the file in a directory.

Not perfect but good enough to get started with.

// A slightly modified version of ipfs-only-hash
// See https://github.com/alanshaw/ipfs-only-hash/issues/18

const block = {
  get: async (cid) => {
    throw new Error(`unexpected block API get for ${cid}`);
  },
  put: async () => {
    throw new Error('unexpected block API put');
  },
};

async function hash(content_, options_) {
  const { importer } = await import('ipfs-unixfs-importer');
  const options = options_ || {};
  options.onlyHash = true;

  let content = content_;
  if (typeof content === 'string') {
    content = [{ content: new TextEncoder().encode(content) }];
  } else if (content instanceof Object.getPrototypeOf(Uint8Array)) {
    content = [{ content }];
  }

  let lastCID;
  for await (const c of importer(content, block, options)) {
    lastCID = c.cid;
  }
  return lastCID;
}

async function hashFiles(path, options) {
  const { globSource } = await import('ipfs-http-client');
  options = {
    cidVersion: 0, // Lines up with the smart contract code
    hidden: true,
    ...options,
  };

  const files = globSource(path, '**');

  const rootCID = await hash(files, options);
  return rootCID;
}

module.exports = {
  cidToHex(cid) {
    return `0x${Buffer.from(cid.bytes.slice(2)).toString('hex')}`;
  },

  of: hash,

  async ofFile(path) {
    return await hashFiles(path, {});
  },

  async ofDir(path) {
    return await hashFiles(path, {
      wrapWithDirectory: true,
    });
  },
};

@rrthomas
Copy link
Author

@wjagodfrey Thanks for this fix! I will use it myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants