From 2f070f2760b5392e30a06786a9c7fa91efefa184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fri=C3=B0j=C3=B3n=20Gu=C3=B0johnsen?= Date: Sun, 13 Jan 2019 22:43:41 +0000 Subject: [PATCH 1/9] feat: create streams --- README.md | 37 ++++++++ lib/api/createreadstream.js | 99 ++++++++++++++++++++ lib/api/createwritestream.js | 175 +++++++++++++++++++++++++++++++++++ lib/smb2.js | 5 +- package.json | 4 +- 5 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 lib/api/createreadstream.js create mode 100644 lib/api/createwritestream.js diff --git a/README.md b/README.md index e1ce82f..2bfdc0e 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,42 @@ smb2Client.rename('path\\to\\my\\file.txt', 'new\\path\\to\\my\\new-file-name.tx }); ``` +### smb2Client.createReadStream ( path ) +Creates a readable stream. +```javascript +var smbStream = smb2Client.createReadStream('path\\to\\my\\file.txt'); +smbStream.pipe(fs.createWriteStream('path/to/local/file.txt')): +smbStream.on('end', function () { + console.log('File copied'); +}); +``` + +Note that when using readable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. + +### smb2Client.createReadStream ( path ) +Creates a readable stream. +```javascript +var smbStream = smb2Client.createReadStream('path\\to\\my\\file.txt'); +smbStream.pipe(fs.createWriteStream('path/to/local/file.txt')): +smbStream.on('end', function () { + console.log('File copied'); +}); +``` + +Note that when using readable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. + +### smb2Client.createWriteStream ( path ) +Creates a writeable stream. +```javascript +var smbStream = smb2Client.createWriteStream('path\\to\\my\\file.txt'); +fs.createReadStream('path/to/local/file.txt').pipe(smbStream); +smbStream.on('finish', function () { + console.log('File copied'); +}); +``` + +Note that when using readable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. + ### smb2Client.close ( ) This function will close the open connection if opened, it will be called automatically after ```autoCloseTimeout``` ms of no SMB2 call on the server. @@ -147,6 +183,7 @@ This function will close the open connection if opened, it will be called automa - [Fabrice Marsaud](https://github.com/marsaud) - [Jay McAliley](https://github.com/jaymcaliley) - [eldrago](https://github.com/eldrago) +- [Friðjón Guðjohnsen](https://github.com/fridjon) ## References diff --git a/lib/api/createreadstream.js b/lib/api/createreadstream.js new file mode 100644 index 0000000..4abec45 --- /dev/null +++ b/lib/api/createreadstream.js @@ -0,0 +1,99 @@ +var RS = require('readable-stream') + , SMB2Forge = require('../tools/smb2-forge') + , SMB2Request = SMB2Forge.request + , SMB2Connection = require('../tools/smb2-connection') + , bigint = require('../tools/bigint') + ; + +/* +* createReadStream +* ================ +* +* Return a read stream for a file on the share +*/ +module.exports = function(filename){ + var connection = this + , file + , bufferedChunks = [] + , readPending = false + , opened = false + , fileLength = 0 + , offset = new bigint(8) + , stop = false + , nbRemainingPackets = 0 + , maxPacketSize = 0x00010000 + , readable = new RS.Readable({ + read: function(size) { + readPending = true; + if (opened) readNext(); + } + }) + ; + + var open = SMB2Connection.requireConnect(function(cb) { + SMB2Request('open', {path:filename}, connection, cb); + }).bind(this); + + open(function(err, openedFile) { + file = openedFile; + opened = true; + if(err) { + readable.emit('error', err); + stop = true; + } + else { + opened = true; + for(var i=0;i 0 && readPending) { + readPending = readable.push(bufferedChunks.shift()); + + if (!offset.lt(fileLength) && bufferedChunks.length === 0 && + nbRemainingPackets === 0 && readPending) { + readable.push(null); + } + } + } + + return readable; +} diff --git a/lib/api/createwritestream.js b/lib/api/createwritestream.js new file mode 100644 index 0000000..dd89b40 --- /dev/null +++ b/lib/api/createwritestream.js @@ -0,0 +1,175 @@ +var RS = require('readable-stream') + , SMB2Forge = require('../tools/smb2-forge') + , SMB2Request = SMB2Forge.request + , SMB2Connection = require('../tools/smb2-connection') + , bigint = require('../tools/bigint') + ; + +/* +* createWriteStream +* ========= +* +* create and return a writeStream to a new file on the share +* +*/ + +module.exports = function(filename) { + var connection = this + , file + , currFileLength = new bigint(8, 0) + , offset = new bigint(8, 0) + , maxPacketSize = new bigint(8, 0x00010000 - 0x71) + , nbRemainingPackets = 0 + , chunkOffset = 0 + , writable = new RS.Writable({ + write: write, + writev: writev, + final: final + }) + , pendingError + , stop = false + , created = false + , writePending = false + , incomingWriteBuffer = [] + , outgoingWriteBuffer = [] + , finalCb = null + ; + + function write(chunk, encoding, cb) { + writev([{chunk: chunk, encoding: encoding}], cb); + } + + function writev(chunks, cb) { + incomingWriteBuffer.push({ chunks: chunks, cb: cb}); + writePending = true; + if (pendingError) { + stop = true; + cb(pendingError); + } else { + if (created) { + writeNext(); + } + } + } + + function final(cb) { + finalCb = cb; + writeNext(); + } + + + var createFile = SMB2Connection.requireConnect(function(cb) { + SMB2Request('create', {path:filename}, connection, cb); + }).bind(this); + + createFile(function(err, f) { + if(err) { + if (outgoingWriteBuffer.length > 0) { + outgoingWriteBuffer[0].cb(err); + stop = true; + } else { + pendingError = err; + } + } + else { + created = true; + file = f; + if (writePending) { + writeNext(); + } + } + }); + + function writeNext() { + if (outgoingWriteBuffer.length !== 0) return; + outgoingWriteBuffer = incomingWriteBuffer; + incomingWriteBuffer = []; + if (outgoingWriteBuffer.length !== 0) startOutgoingBufferWrite(); + if (outgoingWriteBuffer.length === 0 && finalCb) { + SMB2Request('close', file, connection, function(err){ + if(err) finalCb(err); + else { + file = null; + finalCb(); + } + }); + } + } + + function getLengthOfChunksInBuffer(buffer) { + return buffer.reduce(function(acc, curr) { + return acc + curr.chunks.reduce(function(acc2, curr2) { + return acc2 + curr2.chunk.length; + },0); + }, 0) + } + + function startOutgoingBufferWrite() { + currFileLength = currFileLength.add(getLengthOfChunksInBuffer(outgoingWriteBuffer)); + SMB2Request('set_info', {FileId:file.FileId, FileInfoClass:'FileEndOfFileInformation', Buffer:currFileLength.toBuffer()}, connection, function(err){ + if(err) { + stop = true; + outgoingWriteBuffer[0].cb(err); + } + else { + chunkOffset = new bigint(8, 0); + continueOutgoingBufferWrite(); + } + }); + } + + function callback(cb) { + return function(err) { + if(stop) return; + if(err) { + cb(err); + stop = true; + } else { + nbRemainingPackets--; + continueOutgoingBufferWrite(); + } + } + } + + function continueOutgoingBufferWrite() { + if (stop || outgoingWriteBuffer.length === 0) return; + var currChunk = outgoingWriteBuffer[0].chunks[0].chunk; + var currChunkLen = new bigint(8, currChunk.length); + var currCb = outgoingWriteBuffer[0].cb; + while(nbRemainingPackets= currChunk.length) { + outgoingWriteBuffer[0].chunks.shift(); + if (outgoingWriteBuffer[0].chunks.length > 0) { + chunkOffset = new bigint(8, 0); + currChunk = outgoingWriteBuffer[0].chunks[0].chunk; + currChunkLen = new bigint(8, currChunk.length); + } else { + outgoingWriteBuffer[0].cb(); + outgoingWriteBuffer.shift(); + if (outgoingWriteBuffer.length > 0) { + chunkOffset = new bigint(8, 0); + currChunk = outgoingWriteBuffer[0].chunks[0].chunk; + currChunkLen = new bigint(8, currChunk.length); + } else { + chunkOffset = new bigint(8, 0); + } + } + } + nbRemainingPackets++; + } + writeNext(); + } + + + return writable; +}; \ No newline at end of file diff --git a/lib/smb2.js b/lib/smb2.js index c2d700b..37d6002 100644 --- a/lib/smb2.js +++ b/lib/smb2.js @@ -99,6 +99,5 @@ proto.readdir = SMB2Connection.requireConnect(require('./api/readdir')); proto.rmdir = SMB2Connection.requireConnect(require('./api/rmdir')); proto.mkdir = SMB2Connection.requireConnect(require('./api/mkdir')); - - - +proto.createReadStream = require('./api/createreadstream'); +proto.createWriteStream = require('./api/createwritestream'); \ No newline at end of file diff --git a/package.json b/package.json index 72d33c8..e945f2e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "url": "https://github.com/bchelli/node-smb2" }, "dependencies": { - "ntlm": "^0.1.3" + "ntlm": "^0.1.3", + "readable-stream": "^3.1.1" }, "keywords": [ "SMB", @@ -28,3 +29,4 @@ "Samba" ] } + From 370dfe9bbb423cec03f14481bee38239761a5c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fri=C3=B0j=C3=B3n=20Gu=C3=B0johnsen?= Date: Sun, 13 Jan 2019 22:44:39 +0000 Subject: [PATCH 2/9] fix: type --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2bfdc0e..8e97f2b 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ smbStream.on('finish', function () { }); ``` -Note that when using readable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. +Note that when using writeable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. ### smb2Client.close ( ) This function will close the open connection if opened, it will be called automatically after ```autoCloseTimeout``` ms of no SMB2 call on the server. From 0827900709330a6928f0f9ff502efdc74ef671f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fri=C3=B0j=C3=B3n=20Gu=C3=B0johnsen?= Date: Sun, 13 Jan 2019 22:51:48 +0000 Subject: [PATCH 3/9] fix: typo --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index 8e97f2b..2d43b33 100644 --- a/README.md +++ b/README.md @@ -151,18 +151,6 @@ smbStream.on('end', function () { Note that when using readable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. -### smb2Client.createReadStream ( path ) -Creates a readable stream. -```javascript -var smbStream = smb2Client.createReadStream('path\\to\\my\\file.txt'); -smbStream.pipe(fs.createWriteStream('path/to/local/file.txt')): -smbStream.on('end', function () { - console.log('File copied'); -}); -``` - -Note that when using readable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. - ### smb2Client.createWriteStream ( path ) Creates a writeable stream. ```javascript From 123f1bf73c2d190005f5d6d2a091b9ff6eb9150e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82moiej=20Wierci=C5=84ski?= Date: Wed, 20 Feb 2019 22:25:16 +0100 Subject: [PATCH 4/9] added .vscode to gitignore, code to TODO.md, package-lock --- .gitignore | 1 + TODO.md | 12 ++++++------ package-lock.json | 33 +++++++++++++++++++++++++++++++++ package.json | 1 - 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 704aecb..f21aec7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store node_modules test.js +.vscode diff --git a/TODO.md b/TODO.md index 410b87f..4ac5ff1 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,12 @@ # TODO: ## New functions -- fs.appendFile(filename, data, [options], callback) -- fs.chmod(path, mode, callback) -- fs.stat(path, callback) -- fs.watchFile(filename, [options], listener) -- fs.unwatchFile(filename, [listener]) -- fs.watch(filename, [options], [listener]) +- `fs.appendFile(filename, data, [options], callback)` +- `fs.chmod(path, mode, callback)` +- `fs.stat(path, callback)` +- `fs.watchFile(filename, [options], listener)` +- `fs.unwatchFile(filename, [listener])` +- `fs.watch(filename, [options], [listener])` ## Implementation on existing functions - support of mode in mkdir diff --git a/package-lock.json b/package-lock.json index 0101846..8ef0d70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,10 +4,43 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, "ntlm": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ntlm/-/ntlm-0.1.3.tgz", "integrity": "sha1-O4FOvFMKHmzXEtzwz1kBVZMRlcE=" + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" } } } diff --git a/package.json b/package.json index e945f2e..075545b 100644 --- a/package.json +++ b/package.json @@ -29,4 +29,3 @@ "Samba" ] } - From 6dde2facd4f43315c4b7c287c9f790770069bb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82moiej=20Wierci=C5=84ski?= Date: Wed, 20 Feb 2019 22:25:41 +0100 Subject: [PATCH 5/9] fix README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d43b33..fc93fd1 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ smb2Client.rename('path\\to\\my\\file.txt', 'new\\path\\to\\my\\new-file-name.tx Creates a readable stream. ```javascript var smbStream = smb2Client.createReadStream('path\\to\\my\\file.txt'); -smbStream.pipe(fs.createWriteStream('path/to/local/file.txt')): +smbStream.pipe(fs.createWriteStream('path/to/local/file.txt')); smbStream.on('end', function () { console.log('File copied'); }); From 26f8ea5cc61aaa558ad11f4060e03c565c313bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82moiej=20Wierci=C5=84ski?= Date: Wed, 20 Feb 2019 22:26:10 +0100 Subject: [PATCH 6/9] simplify computing of fileLength --- lib/api/createreadstream.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/api/createreadstream.js b/lib/api/createreadstream.js index 4abec45..677f8e4 100644 --- a/lib/api/createreadstream.js +++ b/lib/api/createreadstream.js @@ -43,9 +43,7 @@ module.exports = function(filename){ } else { opened = true; - for(var i=0;i Date: Thu, 21 Feb 2019 00:39:24 +0100 Subject: [PATCH 7/9] added options to createReadStream with start, end properties --- lib/api/createreadstream.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/api/createreadstream.js b/lib/api/createreadstream.js index 677f8e4..03593ee 100644 --- a/lib/api/createreadstream.js +++ b/lib/api/createreadstream.js @@ -11,14 +11,16 @@ var RS = require('readable-stream') * * Return a read stream for a file on the share */ -module.exports = function(filename){ +module.exports = function(filename, options){ + options = __processOptions(options); + var connection = this , file , bufferedChunks = [] , readPending = false , opened = false , fileLength = 0 - , offset = new bigint(8) + , offset = new bigint(8).add(options.start) , stop = false , nbRemainingPackets = 0 , maxPacketSize = 0x00010000 @@ -44,6 +46,10 @@ module.exports = function(filename){ else { opened = true; fileLength = file.EndofFile.readUInt16LE(); + if(options.start >= fileLength) { + readable.emit('error', new Error('start (' + options.start + ') is greater equal than file length (' + fileLength + ')!')); + } + fileLength = Math.min(fileLength, options.end + 1); if (readPending) { readNext(); } @@ -95,3 +101,28 @@ module.exports = function(filename){ return readable; } + +function __processOptions(options) { + if (options === undefined) + options = {}; + else if (options === null || typeof options !== 'object') + throw new TypeError('"options" argument must be a string or an object'); + else + options = Object.create(options); + + if (options.start === undefined) + options.start = 0; + if (typeof options.start !== 'number') + throw new TypeError('start (' + options.start + ') must be a Number'); + + if (options.end === undefined) + options.end = Infinity; + else if (typeof options.end !== 'number') + throw new TypeError('end (' + options.end + ') must be a Number'); + + if (options.start > options.end) + throw new Error('start (' + options.start + ') must be <= end (' + options.end + ')'); + else if (options.start < 0) + throw new Error('start (' + options.start + ') must be >= zero'); + return options; +} From aea3b640c396dc0edf74181db2b8ceb83e949775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82moiej=20Wierci=C5=84ski?= Date: Thu, 21 Feb 2019 00:53:30 +0100 Subject: [PATCH 8/9] update documentation with new functionality --- README.md | 55 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index fc93fd1..d2e6c52 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,15 @@ npm install -S smb2 ### var smb2Client = new SMB2 ( options ) The SMB2 class is the constructor of your SMB2 client. -the parameter ```options``` accepts this list of attributes: +the parameter `options` accepts this list of attributes: -- ```share``` (mandatory): the share you want to access -- ```domain``` (mandatory): the domain of which the user is registred -- ```username``` (mandatory): the username of the user that access the share -- ```password``` (mandatory): the password -- ```port``` (optional): default ```445```, the port of the SMB server -- ```packetConcurrency``` (optional): default ```20```, the number of simulatanous packet when writting / reading data from the share -- ```autoCloseTimeout``` (optional): default ```10000```, the timeout in milliseconds before to close the SMB2 session and the socket, if setted to ```0``` the connection will never be closed unless you do it +- `share` (mandatory): the share you want to access +- `domain` (mandatory): the domain of which the user is registred +- `username` (mandatory): the username of the user that access the share +- `password` (mandatory): the password +- `port` (optional): default `445`, the port of the SMB server +- `packetConcurrency` (optional): default `20`, the number of simulatanous packet when writting / reading data from the share +- `autoCloseTimeout` (optional): default `10000`, the timeout in milliseconds before to close the SMB2 session and the socket, if setted to `0` the connection will never be closed unless you do it Example: ```javascript @@ -55,10 +55,10 @@ smb2Client.readdir('Windows\\System32', function(err, files){ ``` ### smb2Client.readFile ( filename, [options], callback ) -- ```filename``` String -- ```options``` Object - - ```encoding``` String | Null default = null -- ```callback``` Function +- `filename` String +- `options` Object + - `encoding` String | Null default = null +- `callback` Function Asynchronously reads the entire contents of a file. Example: ```javascript @@ -72,11 +72,11 @@ The callback is passed two arguments (err, data), where data is the contents of If no encoding is specified, then the raw buffer is returned. ### smb2Client.writeFile ( filename, data, [options], callback ) -- ```filename``` String -- ```data``` String | Buffer -- ```options``` Object - - ```encoding``` String | Null default = 'utf8' -- ```callback``` Function +- `filename` String +- `data` String | Buffer +- `options` Object + - `encoding` String | Null default = 'utf8' +- `callback` Function Asynchronously writes data to a file, replacing the file if it already exists. data can be a string or a buffer. @@ -139,23 +139,37 @@ smb2Client.rename('path\\to\\my\\file.txt', 'new\\path\\to\\my\\new-file-name.tx }); ``` -### smb2Client.createReadStream ( path ) +### smb2Client.createReadStream ( path, [options] ) +- `path` String +- `options` Object + - `start` Number default = '0' + - `end` Number default = 'Inf' + Creates a readable stream. +`options` can include `start` and `end` values to read a range of bytes from the file instead of the entire file. Both `start` and `end` are inclusive and start counting at 0. + ```javascript var smbStream = smb2Client.createReadStream('path\\to\\my\\file.txt'); -smbStream.pipe(fs.createWriteStream('path/to/local/file.txt')); +smbStream.pipe(fs.createWriteStream('path\\to\\local\\file.txt')); smbStream.on('end', function () { console.log('File copied'); }); ``` +An example to read the last 10 bytes of a file which is 100 bytes long: + +```javascript +smb2Client.createReadStream('sample.txt', { start: 90, end: 99 }); +``` + Note that when using readable streams, it might make sense to disable auto closing of the SMB2 session by setting the `autoCloseTimeout`to `0`. +Note that when `start` > file lenght stream fails with error. (`end` still can be greater than file lenght e.g. `Inf`) ### smb2Client.createWriteStream ( path ) Creates a writeable stream. ```javascript var smbStream = smb2Client.createWriteStream('path\\to\\my\\file.txt'); -fs.createReadStream('path/to/local/file.txt').pipe(smbStream); +fs.createReadStream('path\\to\\local\\file.txt').pipe(smbStream); smbStream.on('finish', function () { console.log('File copied'); }); @@ -172,6 +186,7 @@ This function will close the open connection if opened, it will be called automa - [Jay McAliley](https://github.com/jaymcaliley) - [eldrago](https://github.com/eldrago) - [Friðjón Guðjohnsen](https://github.com/fridjon) +- [Bartłomiej Wierciński](https://github.com/bwiercinski) ## References From 02045a8999bbd59d250231a8aa1857b5063f3c15 Mon Sep 17 00:00:00 2001 From: Ben Chelli Date: Sun, 12 May 2019 14:20:27 -0400 Subject: [PATCH 9/9] Close cleanup + reorder chuncks based actual order of chuncks --- lib/api/createreadstream.js | 114 +++++++++++++++++++++--------------- package.json | 2 +- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/lib/api/createreadstream.js b/lib/api/createreadstream.js index 03593ee..250cf19 100644 --- a/lib/api/createreadstream.js +++ b/lib/api/createreadstream.js @@ -1,7 +1,6 @@ var RS = require('readable-stream') , SMB2Forge = require('../tools/smb2-forge') , SMB2Request = SMB2Forge.request - , SMB2Connection = require('../tools/smb2-connection') , bigint = require('../tools/bigint') ; @@ -12,12 +11,13 @@ var RS = require('readable-stream') * Return a read stream for a file on the share */ module.exports = function(filename, options){ - options = __processOptions(options); + options = processOptions(options); var connection = this , file , bufferedChunks = [] - , readPending = false + , chunckPushed = 0 + , chunckRequested = 0 , opened = false , fileLength = 0 , offset = new bigint(8).add(options.start) @@ -26,56 +26,71 @@ module.exports = function(filename, options){ , maxPacketSize = 0x00010000 , readable = new RS.Readable({ read: function(size) { - readPending = true; - if (opened) readNext(); + if (opened) { + readNext(); + } } }) ; - - var open = SMB2Connection.requireConnect(function(cb) { - SMB2Request('open', {path:filename}, connection, cb); - }).bind(this); - open(function(err, openedFile) { + var close = function () { + stop = true; + if (opened) { + opened = false; + SMB2Request('close', file, connection, function(err){ + if(err){ + readable.emit('error', err); + } + }); + } + }; + + SMB2Request('open', {path:filename}, connection, function(err, openedFile) { file = openedFile; opened = true; if(err) { - readable.emit('error', err); - stop = true; - } - else { - opened = true; + readable.emit('error', err); + return close(); + } else { fileLength = file.EndofFile.readUInt16LE(); if(options.start >= fileLength) { readable.emit('error', new Error('start (' + options.start + ') is greater equal than file length (' + fileLength + ')!')); + return close(); } fileLength = Math.min(fileLength, options.end + 1); - if (readPending) { - readNext(); - } + readNext(); } }); - function callback(offset) { + function callback(position, offset) { return function(err, content){ - if(stop) return; + if(stop) { + return close(); + } if(err) { readable.emit('error', err) - stop = true; + return close(); } else { - bufferedChunks.push(content); + bufferedChunks[position] = { loaded: true, content: content }; + if (position === chunckPushed) { + while (bufferedChunks[chunckPushed] && bufferedChunks[chunckPushed].loaded) { + readable.push(bufferedChunks[chunckPushed].content); + delete bufferedChunks[chunckPushed]; + chunckPushed++; + } + } nbRemainingPackets--; readNext(); + if (chunckPushed == chunckRequested && nbRemainingPackets == 0) { + return close(); + } } } } function readNext() { - if (!readPending) { - return; - } - while(nbRemainingPackets 0 && readPending) { - readPending = readable.push(bufferedChunks.shift()); - - if (!offset.lt(fileLength) && bufferedChunks.length === 0 && - nbRemainingPackets === 0 && readPending) { - readable.push(null); - } - } } return readable; } -function __processOptions(options) { - if (options === undefined) +function processOptions(options) { + + // create the options object + if (options === undefined) { options = {}; - else if (options === null || typeof options !== 'object') + } else if (typeof options === 'string') { + options = { encoding: options }; + } else if (typeof options === 'object') { + // this is a valid options object + } else { throw new TypeError('"options" argument must be a string or an object'); - else - options = Object.create(options); + } - if (options.start === undefined) + // check the start option + if (options.start === undefined) { options.start = 0; - if (typeof options.start !== 'number') + } else if (typeof options.start !== 'number') { throw new TypeError('start (' + options.start + ') must be a Number'); + } - if (options.end === undefined) + // check the end option + if (options.end === undefined) { options.end = Infinity; - else if (typeof options.end !== 'number') + } else if (typeof options.end !== 'number') { throw new TypeError('end (' + options.end + ') must be a Number'); + } - if (options.start > options.end) + // check logical values for start and end + if (options.start > options.end) { throw new Error('start (' + options.start + ') must be <= end (' + options.end + ')'); - else if (options.start < 0) + } else if (options.start < 0) { throw new Error('start (' + options.start + ') must be >= zero'); + } + return options; + } diff --git a/package.json b/package.json index 075545b..d209363 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "smb2", "description": "SMB2 Client", "homepage": "https://github.com/bchelli/node-smb2", - "version": "0.2.11", + "version": "0.2.12", "engines": [ "node" ],