From 52d3d3863dce4509f2ef7d76c98ea3e2c2cdc4b1 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 12 Dec 2019 14:40:14 +0000 Subject: [PATCH 01/12] docs: add mode and mtime and .touch/.chmod mfs commands --- SPEC/FILES.md | 94 +++++++++++++++++++++++++ package.json | 4 +- src/files-mfs/chmod.js | 55 +++++++++++++++ src/files-mfs/cp.js | 56 +++++++++++++++ src/files-mfs/index.js | 4 +- src/files-mfs/ls-pull-stream.js | 10 +-- src/files-mfs/ls-readable-stream.js | 10 +-- src/files-mfs/ls.js | 13 ++-- src/files-mfs/mkdir.js | 24 +++++++ src/files-mfs/touch.js | 70 ++++++++++++++++++ src/files-mfs/write.js | 28 ++++++++ src/files-regular/add-from-fs.js | 3 +- src/files-regular/add.js | 19 +++++ src/files-regular/ls-pull-stream.js | 18 +++-- src/files-regular/ls-readable-stream.js | 18 +++-- src/files-regular/ls.js | 38 ++++++++-- 16 files changed, 429 insertions(+), 35 deletions(-) create mode 100644 src/files-mfs/chmod.js create mode 100644 src/files-mfs/touch.js diff --git a/SPEC/FILES.md b/SPEC/FILES.md index b9b6aaf15..bf044e38b 100644 --- a/SPEC/FILES.md +++ b/SPEC/FILES.md @@ -24,6 +24,7 @@ The regular, top-level API for add, cat, get and ls Files on IPFS The Files API, aka MFS (Mutable File System) _Explore the Mutable File System through interactive coding challenges in our [ProtoSchool tutorial](https://proto.school/#/mutable-file-system/)._ + - [files.chmod](#fileschmod) - [files.cp](#filescp) - [files.flush](#filesflush) - [files.ls](#filesls) @@ -36,6 +37,7 @@ _Explore the Mutable File System through interactive coding challenges in our [P - [files.readReadableStream](#filesreadreadablestream) - [files.rm](#filesrm) - [files.stat](#filesstat) + - [files.touch](#filestouch) - [files.write](#fileswrite) ### ⚠️ Note @@ -58,6 +60,8 @@ Where `data` may be: { path: '/tmp/myfile.txt', // The file path content: // A Buffer, Readable Stream, Pull Stream or File with the contents of the file + mode: '0755' // optional string or integer mode to store the entry with + mtime: 192399399 // optional integer mtime to store the entry with } ``` If no `content` is passed, then the path is treated as an empty directory @@ -759,6 +763,54 @@ A great source of [examples][] can be found in the tests for this API. The Mutable File System (MFS) is a virtual file system on top of IPFS that exposes a Unix like API over a virtual directory. It enables users to write and read from paths without having to worry about updating the graph. It enables things like [ipfs-blob-store](https://github.com/ipfs/ipfs-blob-store) to exist. +#### `files.chmod` + +> Change mode for files and directories + +##### `ipfs.files.chmod(path, mode, [options])` + +Where: + +- `path` is the path to the entry to modify. It might be: + - An existing MFS path to a file or a directory (e.g. `/my-dir/my-file.txt`) + - An IPFS path (e.g. `/ipfs/QmWGeRAEgtsHW3ec7U4qW2CyVy7eA2mFRVbk1nb24jFyks`) + - A [CID][cid] instance (e.g. `new CID('QmWGeRAEgtsHW3ec7U4qW2CyVy7eA2mFRVbk1nb24jFyks')`) +- `mode` is the new file mode. It might be: + - A string octal, e.g. `'0755'` + - A string modification of the existing mode, e.g. `'+x'`, `'-gw'`, etc + - An integer, e.g. the returned value from `parseInt('0755', 8)` + - null, in which case the mode property will be removed +- `options` is an optional Object that might contain the following keys: + - `recursive` is a Boolean value that indicates if `mode` should be applied to all sub files/directories of `path` (default: false) + - `format` is what type of nodes to write any modified entries as (default: `dag-pb`) + - `hashAlg` is which algorithm to use when creating CIDs for modified entries. (default: `sha2-256`) [The list of all possible values]( https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343) + - `flush` is a Boolean value to decide whether or not to immediately flush MFS changes to disk (default: true) + - `cidVersion`: the CID version to use for any updated entries (integer, default 0) + +UnixFS entries are stored as protobufs and since protobufs cannot represent `null` values, setting `mode` to `0` will reset the mode to the default value of `0644` for files and `0755` for directories. + +**Returns** + +| Type | Description | +| -------- | -------- | +| `Promise` | If action is successfully completed. Otherwise an error will be thrown | + +**Example:** + +```JavaScript +// To give a file -rwxrwxrwx permissions +await ipfs.files.chmod('/path/to/file.txt', parseInt('0777', 8)) + +// Alternatively +await ipfs.files.chmod('/path/to/file.txt', '+rwx') + +// You can omit the leading `0` too +await ipfs.files.chmod('/path/to/file.txt', '777') + +// Reset a file to default permissions +await ipfs.files.chmod('/path/to/file.txt', null) +``` + #### `files.cp` > Copy files. @@ -821,6 +873,8 @@ Where: - `format` is what type of nodes to write any newly created directories as (default: `dag-pb`) - `hashAlg` is which algorithm to use when creating CIDs for newly created directories (default: `sha2-256`) [The list of all possible values]( https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343) - `flush` is a Boolean value to decide whether or not to immediately flush MFS changes to disk (default: true) + - `mode`: optional UnixFS mode to create the directory with + - `mtime`: optional modification time to create the directory with **Returns** @@ -884,6 +938,44 @@ console.log(stats) // } ``` +#### `files.touch` + +> Update the mtime of a file or directory + +##### `ipfs.files.touch(path, [mtime], [options])` + +Where: + +- `path` is the path to the file or directory to update. It might be: + - An existing MFS path to a file or directory (e.g. `/my-dir/a.txt`) + - An IPFS path (e.g. `/ipfs/QmWGeRAEgtsHW3ec7U4qW2CyVy7eA2mFRVbk1nb24jFyks`) + - A [CID][cid] instance (e.g. `new CID('QmWGeRAEgtsHW3ec7U4qW2CyVy7eA2mFRVbk1nb24jFyks')`) +- `mtime` is the time in seconds since or before the Unix Epoch to set the mtime to, or null to remove the mtime property (default: now) +- `options` is an optional Object that might contain the following keys: + - `format` is what type of nodes to write any modified entries as (default: `dag-pb`) + - `hashAlg` is which algorithm to use when creating CIDs for modified entries. (default: `sha2-256`) [The list of all possible values]( https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343) + - `flush` is a Boolean value to decide whether or not to immediately flush MFS changes to disk (default: true) + - `cidVersion`: the CID version to use for any updated entries (integer, default 0) + +**Returns** + +| Type | Description | +| -------- | -------- | +| `Promise` | If action is successfully completed. Otherwise an error will be thrown | + +**Example:** + +```JavaScript +// set the mtime to the current time +await ipfs.files.touch('/path/to/file.txt') + +// set the mtime to a specific time +await ipfs.files.touch('/path/to/file.txt', Math.round(new Date('May 23, 2014 14:45:14 -0700').getTime() / 1000)) + +// remove the mtime property +await ipfs.files.touch('/path/to/file.txt', null) +``` + #### `files.rm` > Remove a file or directory. @@ -1036,6 +1128,8 @@ Where: - `length` is an Integer with the maximum number of bytes to read (default: Read all bytes from `content`) - `rawLeaves`: if true, DAG leaves will contain raw file data and not be wrapped in a protobuf (boolean, default false) - `cidVersion`: the CID version to use when storing the data (storage keys are based on the CID, including its version) (integer, default 0) + - `mode`: optional UnixFS mode to create or update the file with + - `mtime`: optional modification to create or update the file with **Returns** diff --git a/package.json b/package.json index 8fc655fac..1214536b9 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "get-stream": "^5.1.0", "hat": "0.0.3", "ipfs-block": "~0.8.0", - "ipfs-unixfs": "~0.1.16", - "ipfs-utils": "~0.4.0", + "ipfs-unixfs": "~0.2.0", + "ipfs-utils": "^0.4.1", "ipld-dag-cbor": "~0.15.0", "ipld-dag-pb": "^0.18.1", "is-ipfs": "~0.6.1", diff --git a/src/files-mfs/chmod.js b/src/files-mfs/chmod.js new file mode 100644 index 000000000..76db1d0c1 --- /dev/null +++ b/src/files-mfs/chmod.js @@ -0,0 +1,55 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.chmod', function () { + this.timeout(40 * 1000) + + let ipfs + + before(async function () { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + ipfs = await common.setup() + }) + + after(() => common.teardown()) + + it('should change file mode', async function () { + const testPath = `/test-${hat()}` + const finalMode = parseInt('544', 8) + + await ipfs.files.write(testPath, Buffer.from('Hello, world!'), { + create: true + }) + await ipfs.files.chmod(testPath, finalMode) + + const stat = await ipfs.files.stat(testPath) + + expect(stat).to.have.property('mode').that.equals(finalMode) + }) + + it('should change directory mode', async function () { + const testPath = `/test-${hat()}` + const finalMode = parseInt('544', 8) + + await ipfs.files.mkdir(testPath, { + create: true + }) + await ipfs.files.chmod(testPath, finalMode) + + const stat = await ipfs.files.stat(testPath) + + expect(stat).to.have.property('mode').that.equals(finalMode) + }) + }) +} diff --git a/src/files-mfs/cp.js b/src/files-mfs/cp.js index 0515d83ed..469cb14fd 100644 --- a/src/files-mfs/cp.js +++ b/src/files-mfs/cp.js @@ -59,5 +59,61 @@ module.exports = (common, options) => { const testFileData = await ipfs.files.read(testFilePath) expect(testFileData).to.eql(fixtures.smallFile.data) }) + + it('should respect metadata when copying files', async function () { + const testSrcPath = `/test-${hat()}` + const testDestPath = `/test-${hat()}` + const mode = parseInt('0321', 8) + const mtime = Math.round(Date.now() / 1000) + + await ipfs.files.write(testSrcPath, Buffer.from('TEST'), { + create: true, + mode, + mtime + }) + await ipfs.files.cp(testSrcPath, testDestPath) + + const stats = await ipfs.files.stat(testDestPath) + expect(stats).to.have.property('mtime', mtime) + expect(stats).to.have.property('mode', mode) + }) + + it('should respect metadata when copying directories', async function () { + const testSrcPath = `/test-${hat()}` + const testDestPath = `/test-${hat()}` + const mode = parseInt('0321', 8) + const mtime = Math.round(Date.now() / 1000) + + await ipfs.files.mkdir(testSrcPath, { + mode, + mtime + }) + await ipfs.files.cp(testSrcPath, testDestPath, { + recursive: true + }) + + const stats = await ipfs.files.stat(testDestPath) + expect(stats).to.have.property('mtime', mtime) + expect(stats).to.have.property('mode', mode) + }) + + it('should respect metadata when copying from outside of mfs', async function () { + const testDestPath = `/test-${hat()}` + const mode = parseInt('0321', 8) + const mtime = Math.round(Date.now() / 1000) + + const [{ + hash + }] = await ipfs.add({ + content: fixtures.smallFile.data, + mode, + mtime + }) + await ipfs.files.cp(`/ipfs/${hash}`, testDestPath) + + const stats = await ipfs.files.stat(testDestPath) + expect(stats).to.have.property('mtime', mtime) + expect(stats).to.have.property('mode', mode) + }) }) } diff --git a/src/files-mfs/index.js b/src/files-mfs/index.js index 6f70a80d7..a5ba1bd15 100644 --- a/src/files-mfs/index.js +++ b/src/files-mfs/index.js @@ -3,6 +3,7 @@ const { createSuite } = require('../utils/suite') const tests = { + chmod: require('./chmod'), mkdir: require('./mkdir'), write: require('./write'), cp: require('./cp'), @@ -15,7 +16,8 @@ const tests = { ls: require('./ls'), lsReadableStream: require('./ls-readable-stream'), lsPullStream: require('./ls-pull-stream'), - flush: require('./flush') + flush: require('./flush'), + touch: require('./touch') } module.exports = createSuite(tests) diff --git a/src/files-mfs/ls-pull-stream.js b/src/files-mfs/ls-pull-stream.js index 0674fa7b7..dce07fbd3 100644 --- a/src/files-mfs/ls-pull-stream.js +++ b/src/files-mfs/ls-pull-stream.js @@ -41,8 +41,8 @@ module.exports = (common, options) => { const entries = await pullToPromise.any(ipfs.files.lsPullStream(testDir)) expect(entries.sort((a, b) => a.name.localeCompare(b.name))).to.eql([ - { name: 'b', type: 0, size: 0, hash: '' }, - { name: 'lv1', type: 0, size: 0, hash: '' } + { name: 'b', type: 0, size: 0, hash: '', mode: parseInt('0644', 8) }, + { name: 'lv1', type: 0, size: 0, hash: '', mode: parseInt('0755', 8) } ]) }) @@ -59,13 +59,15 @@ module.exports = (common, options) => { name: 'b', type: 0, size: 13, - hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T' + hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T', + mode: parseInt('0644', 8) }, { name: 'lv1', type: 1, size: 0, - hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + mode: parseInt('0755', 8) } ]) }) diff --git a/src/files-mfs/ls-readable-stream.js b/src/files-mfs/ls-readable-stream.js index fab1c3ad6..f3a71e650 100644 --- a/src/files-mfs/ls-readable-stream.js +++ b/src/files-mfs/ls-readable-stream.js @@ -44,8 +44,8 @@ module.exports = (common, options) => { const entries = await getStream.array(stream) expect(entries.sort((a, b) => a.name.localeCompare(b.name))).to.eql([ - { name: 'b', type: 0, size: 0, hash: '' }, - { name: 'lv1', type: 0, size: 0, hash: '' } + { name: 'b', type: 0, size: 0, hash: '', mode: parseInt('0644', 8) }, + { name: 'lv1', type: 0, size: 0, hash: '', mode: parseInt('0755', 8) } ]) }) @@ -63,13 +63,15 @@ module.exports = (common, options) => { name: 'b', type: 0, size: 13, - hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T' + hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T', + mode: parseInt('0644', 8) }, { name: 'lv1', type: 1, size: 0, - hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + mode: parseInt('0755', 8) } ]) }) diff --git a/src/files-mfs/ls.js b/src/files-mfs/ls.js index 5497f49ab..eee1c982d 100644 --- a/src/files-mfs/ls.js +++ b/src/files-mfs/ls.js @@ -38,8 +38,8 @@ module.exports = (common, options) => { const info = await ipfs.files.ls(testDir) expect(info.sort((a, b) => a.name.localeCompare(b.name))).to.eql([ - { name: 'b', type: 0, size: 0, hash: '' }, - { name: 'lv1', type: 0, size: 0, hash: '' } + { name: 'b', type: 0, size: 0, hash: '', mode: parseInt('0644', 8) }, + { name: 'lv1', type: 0, size: 0, hash: '', mode: parseInt('0755', 8) } ]) }) @@ -56,13 +56,15 @@ module.exports = (common, options) => { name: 'b', type: 0, size: 13, - hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T' + hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T', + mode: parseInt('0644', 8) }, { name: 'lv1', type: 1, size: 0, - hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + mode: parseInt('0755', 8) } ]) }) @@ -97,7 +99,8 @@ module.exports = (common, options) => { hash: '', name: fileName, size: 0, - type: 0 + type: 0, + mode: parseInt('0644', 8) }]) }) }) diff --git a/src/files-mfs/mkdir.js b/src/files-mfs/mkdir.js index 0c35e5ec8..545fac6f4 100644 --- a/src/files-mfs/mkdir.js +++ b/src/files-mfs/mkdir.js @@ -37,5 +37,29 @@ module.exports = (common, options) => { it('should not make already existent directory', () => { return expect(ipfs.files.mkdir('/')).to.eventually.be.rejected() }) + + it('should make directory and specify mode', async function () { + const testPath = `/test-${hat()}` + const mode = parseInt('0321', 8) + + await ipfs.files.mkdir(testPath, { + mode + }) + + const stats = await ipfs.files.stat(testPath) + expect(stats).to.have.property('mode', mode) + }) + + it('should make directory and specify mtime', async function () { + const testPath = `/test-${hat()}` + const mtime = Math.round(Date.now() / 1000) + + await ipfs.files.mkdir(testPath, { + mtime + }) + + const stats = await ipfs.files.stat(testPath) + expect(stats).to.have.property('mtime', mtime) + }) }) } diff --git a/src/files-mfs/touch.js b/src/files-mfs/touch.js new file mode 100644 index 000000000..f75e4834c --- /dev/null +++ b/src/files-mfs/touch.js @@ -0,0 +1,70 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { fixtures } = require('../files-regular/utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') +const delay = require('delay') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.touch', function () { + this.timeout(10 * 1000) + + let ipfs + + before(async function () { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + ipfs = await common.setup() + }) + before(async () => { await ipfs.add(fixtures.smallFile.data) }) + + after(() => common.teardown()) + + it('should update file mtime', async function () { + this.slow(5 * 1000) + const testPath = `/test-${hat()}` + + await ipfs.files.write(testPath, Buffer.from('Hello, world!'), { + create: true, + mtime: Math.round(Date.now() / 1000) + }) + + const stat = await ipfs.files.stat(testPath) + + await delay(2000) + + await ipfs.files.touch(testPath) + + const stat2 = await ipfs.files.stat(testPath) + + expect(stat2).to.have.property('mtime').that.is.greaterThan(stat.mtime) + }) + + it('should update directory mtime', async function () { + this.slow(5 * 1000) + const testPath = `/test-${hat()}` + + await ipfs.files.mkdir(testPath, { + create: true, + mtime: Math.round(Date.now() / 1000) + }) + + const stat = await ipfs.files.stat(testPath) + + await delay(2000) + + await ipfs.files.touch(testPath) + + const stat2 = await ipfs.files.stat(testPath) + + expect(stat2).to.have.property('mtime').that.is.greaterThan(stat.mtime) + }) + }) +} diff --git a/src/files-mfs/write.js b/src/files-mfs/write.js index 0fb3a72eb..02da4e743 100644 --- a/src/files-mfs/write.js +++ b/src/files-mfs/write.js @@ -45,5 +45,33 @@ module.exports = (common, options) => { const stats = await ipfs.files.stat(testPath) expect(stats.type).to.equal('file') }) + + it('should write file and specify mode', async function () { + const testPath = `/test-${hat()}` + const mode = parseInt('0321', 8) + + await ipfs.files.write(testPath, Buffer.from('Hello, world!'), { + create: true, + parents: true, + mode + }) + + const stats = await ipfs.files.stat(testPath) + expect(stats).to.have.property('mode', mode) + }) + + it('should write file and specify mtime', async function () { + const testPath = `/test-${hat()}` + const mtime = Math.round(Date.now() / 1000) + + await ipfs.files.write(testPath, Buffer.from('Hello, world!'), { + create: true, + parents: true, + mtime + }) + + const stats = await ipfs.files.stat(testPath) + expect(stats).to.have.property('mtime', mtime) + }) }) } diff --git a/src/files-regular/add-from-fs.js b/src/files-regular/add-from-fs.js index a20a01718..db8a1bd2b 100644 --- a/src/files-regular/add-from-fs.js +++ b/src/files-regular/add-from-fs.js @@ -44,7 +44,8 @@ module.exports = (common, options) => { const filesPath = path.join(fixturesPath, 'test-folder') const result = await ipfs.addFromFs(filesPath, { recursive: true, ignore: ['files/**'] }) - expect(result.length).to.be.below(9) + + expect(result.some(file => file.path.includes('test-folder/files/'))).to.be.false() }) it('should add a file from the file system', async () => { diff --git a/src/files-regular/add.js b/src/files-regular/add.js index c1fbaaa79..c5833e2cb 100644 --- a/src/files-regular/add.js +++ b/src/files-regular/add.js @@ -299,5 +299,24 @@ module.exports = (common, options) => { await expectTimeout(ipfs.object.get(files[0].hash), 4000) }) + + it('should add with metadata', async function () { + this.slow(10 * 1000) + const content = String(Math.random() + Date.now()) + const mtime = Math.round(Date.now() / 1000) + const mode = parseInt('0777', 8) + + const files = await ipfs.add({ + content: Buffer.from(content), + mtime, + mode + }) + expect(files).to.have.length(1) + + const stats = await ipfs.files.stat(`/ipfs/${files[0].hash}`) + + expect(stats).to.have.property('mtime', mtime) + expect(stats).to.have.property('mode', mode) + }) }) } diff --git a/src/files-regular/ls-pull-stream.js b/src/files-regular/ls-pull-stream.js index 699574fa1..63bba5744 100644 --- a/src/files-regular/ls-pull-stream.js +++ b/src/files-regular/ls-pull-stream.js @@ -61,7 +61,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', size: 11685, hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -69,7 +70,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', size: 0, hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', - type: 'dir' + type: 'dir', + mode: parseInt('0755', 8) }, { depth: 1, @@ -77,7 +79,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', size: 0, hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', - type: 'dir' + type: 'dir', + mode: parseInt('0755', 8) }, { depth: 1, @@ -85,7 +88,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', size: 581878, hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -93,7 +97,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', size: 2294, hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -101,7 +106,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', size: 4540, hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) } ]) }) diff --git a/src/files-regular/ls-readable-stream.js b/src/files-regular/ls-readable-stream.js index 42e39fe01..3aeebf170 100644 --- a/src/files-regular/ls-readable-stream.js +++ b/src/files-regular/ls-readable-stream.js @@ -61,7 +61,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', size: 11685, hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -69,7 +70,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', size: 0, hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', - type: 'dir' + type: 'dir', + mode: parseInt('0755', 8) }, { depth: 1, @@ -77,7 +79,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', size: 0, hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', - type: 'dir' + type: 'dir', + mode: parseInt('0755', 8) }, { depth: 1, @@ -85,7 +88,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', size: 581878, hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -93,7 +97,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', size: 2294, hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -101,7 +106,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', size: 4540, hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) } ]) }) diff --git a/src/files-regular/ls.js b/src/files-regular/ls.js index 4c3654870..776299d70 100644 --- a/src/files-regular/ls.js +++ b/src/files-regular/ls.js @@ -62,7 +62,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', size: 11685, hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -70,7 +71,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', size: 0, hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', - type: 'dir' + type: 'dir', + mode: parseInt('0755', 8) }, { depth: 1, @@ -78,7 +80,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', size: 0, hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', - type: 'dir' + type: 'dir', + mode: parseInt('0755', 8) }, { depth: 1, @@ -86,7 +89,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', size: 581878, hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -94,7 +98,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', size: 2294, hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) }, { depth: 1, @@ -102,7 +107,8 @@ module.exports = (common, options) => { path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', size: 4540, hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', - type: 'file' + type: 'file', + mode: parseInt('0644', 8) } ]) }) @@ -177,5 +183,25 @@ module.exports = (common, options) => { expect(res.find(file => file.hash === hash)).to.exist() }) }) + + it('should ls with metadata', async () => { + const dir = randomName('DIR') + const mtime = Math.round(Date.now() / 1000) + const mode = parseInt('0532', 8) + + const input = [ + { path: `${dir}/${randomName('F0')}`, content: Buffer.from(randomName('D0')), mode, mtime }, + { path: `${dir}/${randomName('F1')}`, content: Buffer.from(randomName('D1')), mode, mtime } + ] + + const res = await ipfs.add(input) + const output = await ipfs.ls(`/ipfs/${res[res.length - 1].hash}`) + + expect(output).to.have.lengthOf(input.length) + expect(output).to.have.nested.property('[0].mtime', mtime) + expect(output).to.have.nested.property('[0].mode', mode) + expect(output).to.have.nested.property('[1].mtime', mtime) + expect(output).to.have.nested.property('[1].mode', mode) + }) }) } From 12a50cc654ad5030344d18e01f47b1724c240514 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 19 Dec 2019 14:30:38 +0000 Subject: [PATCH 02/12] docs: update arg/return types --- SPEC/FILES.md | 74 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/SPEC/FILES.md b/SPEC/FILES.md index bf044e38b..387029931 100644 --- a/SPEC/FILES.md +++ b/SPEC/FILES.md @@ -60,8 +60,8 @@ Where `data` may be: { path: '/tmp/myfile.txt', // The file path content: // A Buffer, Readable Stream, Pull Stream or File with the contents of the file - mode: '0755' // optional string or integer mode to store the entry with - mtime: 192399399 // optional integer mtime to store the entry with + mode: '0755' // optional string or integer mode to store the entry with. strings will be interpreted as a base 8 number + mtime: Date // optional mtime to store the entry with } ``` If no `content` is passed, then the path is treated as an empty directory @@ -102,6 +102,8 @@ an array of objects is returned, each of the form: { path: '/tmp/myfile.txt', hash: 'QmHash', // base58 encoded multihash + mode: Number, + mtime: Date, size: 123 } ``` @@ -134,16 +136,20 @@ const results = await ipfs.add(files) The `results` array: -```json +```javascript [ { "path": "tmp", "hash": "QmWXdjNC362aPDtwHPUE9o2VMqPeNeCQuTBTv1NsKtwypg", + "mode": 493, + "mtime": Date, "size": 67 }, { "path": "/tmp/myfile.txt", "hash": "QmNz1UBzpdd4HfZ3qir3aPiRdX5a93XwTuDNyXRc6PKhWW", + "mode": 420, + "mtime": Date, "size": 11 } ] @@ -183,6 +189,8 @@ stream.on('data', function (file) { // { // path: '/tmp/myfile.txt', // hash: 'QmHash' // base58 encoded multihash + // mode: Number, + // mtime: Date, // size: 123 // } }) @@ -211,6 +219,8 @@ Returns a Pull Stream, where objects can be written of the forms { path: '/tmp/myfile.txt', // The file path content: // A Buffer, Readable Stream, Pull Stream or File with the contents of the file + mode: '0755' // optional string or integer mode to store the entry with. strings will be interpreted as a base 8 number + mtime: Date // optional mtime to store the entry with } ``` @@ -237,6 +247,8 @@ pull( // { // path: '/tmp/myfile.txt', // hash: 'QmHash' // base58 encoded multihash + // mode: Number + // mtime: Date // size: 123 // } }) @@ -268,6 +280,8 @@ an array of objects is returned, each of the form: { path: 'test-folder', hash: 'QmRNjDeKStKGTQXnJ2NFqeQ9oW23WcpbmvCVrpDHgDg3T6', + mode: Number + mtime: Date size: 123 } ``` @@ -322,6 +336,8 @@ an array of objects is returned, each of the form: { path: '/tmp/myfile.txt', hash: 'QmHash', // base58 encoded multihash + mode: Number, + mtime: Date, size: 123 } ``` @@ -515,7 +531,9 @@ the yielded objects are of the form: ```js { path: '/tmp/myfile.txt', - content: + content: , + mode: Number, + mtime: Date } ``` @@ -567,7 +585,9 @@ the yielded objects are of the form: ```js { path: '/tmp/myfile.txt', - content: + content: , + mode: Number, + mtime: Date } ``` @@ -628,7 +648,9 @@ an array of objects is returned, each of the form: path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', size: 11696, hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' + type: 'file', + mode: Number, + mtime: Date } ``` @@ -678,7 +700,9 @@ the yielded objects are of the form: path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', size: 11696, hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' + type: 'file', + mode: Number, + mtime: Date } ``` @@ -731,7 +755,9 @@ the yielded objects are of the form: path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', size: 11696, hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' + type: 'file', + mode: Number, + mtime: Date } ``` @@ -779,7 +805,6 @@ Where: - A string octal, e.g. `'0755'` - A string modification of the existing mode, e.g. `'+x'`, `'-gw'`, etc - An integer, e.g. the returned value from `parseInt('0755', 8)` - - null, in which case the mode property will be removed - `options` is an optional Object that might contain the following keys: - `recursive` is a Boolean value that indicates if `mode` should be applied to all sub files/directories of `path` (default: false) - `format` is what type of nodes to write any modified entries as (default: `dag-pb`) @@ -787,8 +812,6 @@ Where: - `flush` is a Boolean value to decide whether or not to immediately flush MFS changes to disk (default: true) - `cidVersion`: the CID version to use for any updated entries (integer, default 0) -UnixFS entries are stored as protobufs and since protobufs cannot represent `null` values, setting `mode` to `0` will reset the mode to the default value of `0644` for files and `0755` for directories. - **Returns** | Type | Description | @@ -806,9 +829,6 @@ await ipfs.files.chmod('/path/to/file.txt', '+rwx') // You can omit the leading `0` too await ipfs.files.chmod('/path/to/file.txt', '777') - -// Reset a file to default permissions -await ipfs.files.chmod('/path/to/file.txt', null) ``` #### `files.cp` @@ -873,8 +893,8 @@ Where: - `format` is what type of nodes to write any newly created directories as (default: `dag-pb`) - `hashAlg` is which algorithm to use when creating CIDs for newly created directories (default: `sha2-256`) [The list of all possible values]( https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343) - `flush` is a Boolean value to decide whether or not to immediately flush MFS changes to disk (default: true) - - `mode`: optional UnixFS mode to create the directory with - - `mtime`: optional modification time to create the directory with + - `mode`: optional UnixFS mode to create the directory with - a number or a string that will be interpreted as a base 8 number + - `mtime`: optional modification [Date] to create the directory with **Returns** @@ -942,7 +962,7 @@ console.log(stats) > Update the mtime of a file or directory -##### `ipfs.files.touch(path, [mtime], [options])` +##### `ipfs.files.touch(path, [options])` Where: @@ -950,8 +970,8 @@ Where: - An existing MFS path to a file or directory (e.g. `/my-dir/a.txt`) - An IPFS path (e.g. `/ipfs/QmWGeRAEgtsHW3ec7U4qW2CyVy7eA2mFRVbk1nb24jFyks`) - A [CID][cid] instance (e.g. `new CID('QmWGeRAEgtsHW3ec7U4qW2CyVy7eA2mFRVbk1nb24jFyks')`) -- `mtime` is the time in seconds since or before the Unix Epoch to set the mtime to, or null to remove the mtime property (default: now) - `options` is an optional Object that might contain the following keys: + - `mtime` is a [Date] that represents the number seconds since or before the Unix Epoch to set the mtime to, or null to remove the mtime property (default: now) - `format` is what type of nodes to write any modified entries as (default: `dag-pb`) - `hashAlg` is which algorithm to use when creating CIDs for modified entries. (default: `sha2-256`) [The list of all possible values]( https://github.com/multiformats/js-multihash/blob/master/src/constants.js#L5-L343) - `flush` is a Boolean value to decide whether or not to immediately flush MFS changes to disk (default: true) @@ -970,10 +990,9 @@ Where: await ipfs.files.touch('/path/to/file.txt') // set the mtime to a specific time -await ipfs.files.touch('/path/to/file.txt', Math.round(new Date('May 23, 2014 14:45:14 -0700').getTime() / 1000)) - -// remove the mtime property -await ipfs.files.touch('/path/to/file.txt', null) +await ipfs.files.touch('/path/to/file.txt', { + mtime: new Date('May 23, 2014 14:45:14 -0700') +}) ``` #### `files.rm` @@ -1128,8 +1147,8 @@ Where: - `length` is an Integer with the maximum number of bytes to read (default: Read all bytes from `content`) - `rawLeaves`: if true, DAG leaves will contain raw file data and not be wrapped in a protobuf (boolean, default false) - `cidVersion`: the CID version to use when storing the data (storage keys are based on the CID, including its version) (integer, default 0) - - `mode`: optional UnixFS mode to create or update the file with - - `mtime`: optional modification to create or update the file with + - `mode`: optional UnixFS mode to create or update the file with - a number or a string that will be interpreted as a base 8 number + - `mtime`: optional modification [Date] to create or update the file with **Returns** @@ -1238,6 +1257,8 @@ each object contains the following keys: - `type` which is the object's type (`directory` or `file`) - `size` the size of the file in bytes - `hash` the hash of the file +- `mode` the UnixFS mode as a Number +- `mtime` the UnixFS mtime as a Date **Example:** @@ -1282,6 +1303,8 @@ the yielded objects contain the following keys: - `type` which is the object's type (`directory` or `file`) - `size` the size of the file in bytes - `hash` the hash of the file +- `mode` the UnixFS mode as a Number +- `mtime` the UnixFS mtime as a Date **Example:** @@ -1324,6 +1347,8 @@ the yielded objects contain the following keys: - `type` which is the object's type (`directory` or `file`) - `size` the size of the file in bytes - `hash` the hash of the file + - `mode` the UnixFS mode as a Number + - `mtime` the UnixFS mtime as a Date **Example:** @@ -1346,3 +1371,4 @@ A great source of [examples][] can be found in the tests for this API. [file]: https://developer.mozilla.org/en-US/docs/Web/API/File [cid]: https://www.npmjs.com/package/cids [blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob +[Date]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date From 522250e2ebd4d8f8a0039db3a81cf7303bd554c7 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 23 Dec 2019 16:03:46 +0000 Subject: [PATCH 03/12] test: updates tests to use timespecs --- SPEC/FILES.md | 39 ++++---- src/files-mfs/chmod.js | 47 ++++++---- src/files-mfs/cp.js | 27 ++++-- src/files-mfs/ls-pull-stream.js | 38 ++++---- src/files-mfs/ls-readable-stream.js | 38 ++++---- src/files-mfs/ls.js | 81 +++++++++------- src/files-mfs/mkdir.js | 72 ++++++++++++--- src/files-mfs/stat.js | 3 +- src/files-mfs/touch.js | 105 ++++++++++++++++----- src/files-mfs/write.js | 82 ++++++++++++---- src/files-regular/add.js | 77 +++++++++++++--- src/files-regular/ls-pull-stream.js | 99 +++++++++----------- src/files-regular/ls-readable-stream.js | 99 +++++++++----------- src/files-regular/ls.js | 118 +++++++++++------------- 14 files changed, 558 insertions(+), 367 deletions(-) diff --git a/SPEC/FILES.md b/SPEC/FILES.md index 387029931..68c0cd9ac 100644 --- a/SPEC/FILES.md +++ b/SPEC/FILES.md @@ -61,7 +61,7 @@ Where `data` may be: path: '/tmp/myfile.txt', // The file path content: // A Buffer, Readable Stream, Pull Stream or File with the contents of the file mode: '0755' // optional string or integer mode to store the entry with. strings will be interpreted as a base 8 number - mtime: Date // optional mtime to store the entry with + mtime: