diff --git a/examples/v1/customWebResource/README.md b/examples/v1/customWebResource/README.md new file mode 100644 index 00000000..7cdb6d84 --- /dev/null +++ b/examples/v1/customWebResource/README.md @@ -0,0 +1,21 @@ +# Custom Web Resource + +This is an example to show how to make a simple custom resource base on the web. It gets a web content as its own content. + +The folder `ts` and the folder `js` are the same thing. The `js` folder display the example in JavaScript while the `ts` display the example in TypeScript. + +## Usage + +### Execute + +```bash +node index.js +``` + +### Tests + +```bash +npm tst +``` + + diff --git a/examples/v1/customWebResource/index.js b/examples/v1/customWebResource/index.js new file mode 100644 index 00000000..367a3bf0 --- /dev/null +++ b/examples/v1/customWebResource/index.js @@ -0,0 +1,45 @@ +const webFsManager = require('./js/fsManager.js'), + webFile = require('./js/resource.js'), + webdav = require('webdav-server'), + zlib = require('zlib'), + fs = require('fs'); + +const server = new webdav.WebDAVServer({ + port: 1900, + autoSave: { + treeFilePath: './data.json', + tempTreeFilePath: './data.tmp.json' + }, + autoLoad: { + treeFilePath: './data.json', + fsManagers: [ + new webdav.RootFSManager(), + new webFsManager.WebFSManager(), + new webdav.VirtualFSManager() + ] + } +}); + +server.autoLoad((e) => { + if(e) + { + server.addResourceTree([ + new webFile.WebFile('http://unlicense.org/UNLICENSE', 'license.txt'), + new webFile.WebFile('https://github.com/OpenMarshal/npm-WebDAV-Server', 'webdav-server-github.html'), + new webFile.WebFile('http://www.stuffedcupcakes.com/wp-content/uploads/2013/05/Chocolate-Overload.jpg', 'chocolate.jpg') + ], (e) => { + if(e) throw e; + + run(); + }); + } + else + run(); +}) + +function run() +{ + server.start((s) => { + console.log('Server started on port ' + s.address().port + '.'); + }); +} diff --git a/examples/v1/customWebResource/js/fsManager.js b/examples/v1/customWebResource/js/fsManager.js new file mode 100644 index 00000000..95b9f9d6 --- /dev/null +++ b/examples/v1/customWebResource/js/fsManager.js @@ -0,0 +1,36 @@ +const webdav = require('webdav-server'), + webFile = require('./resource.js'); + +module.exports.WebFSManager = function() +{ + const fsManager = { }; + fsManager.uid = 'WebFSManager_1.0.0'; + + fsManager.serialize = function(resource, obj) + { + return { + dateCreation: resource.dateCreation, + dateLastModified: resource.dateLastModified, + properties: resource.properties, + webUrl: resource.webUrl, + fileName: resource.fileName, + refreshTimeoutMS: resource.refreshTimeoutMS + }; + } + + fsManager.unserialize = function(data, obj) + { + const rs = new webFile.WebFile(data.webUrl, data.fileName, data.refreshTimeoutMS); + rs.dateCreation = data.dateCreation; + rs.dateLastModified = data.dateLastModified; + rs.properties = data.properties; + return rs; + } + + fsManager.newResource = function(fullPath, name, type, parent) + { + throw webdav.Errors.InvalidOperation; + } + + return fsManager; +} diff --git a/examples/v1/customWebResource/js/resource.js b/examples/v1/customWebResource/js/resource.js new file mode 100644 index 00000000..231d442d --- /dev/null +++ b/examples/v1/customWebResource/js/resource.js @@ -0,0 +1,105 @@ +"use strict"; +const webdav = require('webdav-server'), + request = require('request'), + webFSManager = require('./fsManager.js'); + +module.exports.WebFile = function(webUrl, fileName, refreshTimeoutMs = 10000) +{ + const stdRes = new webdav.StandardResource(null, new webFSManager.WebFSManager()); + + stdRes.refreshTimeoutMs = refreshTimeoutMs; + stdRes.lenUpdateTime = 0; + stdRes.fileName = fileName; + stdRes.webUrl = webUrl; + stdRes.len = -1; + + stdRes.openReadStream = function() + { + let size = 0; + const stream = request.get(this.webUrl); + stream.on('data', (chunk) => { + size = chunk.length; + }) + stream.on('end', () => { + this.len = size; + this.lenUpdateTime = Date.now(); + }) + stream.end(); + return stream; + }; + + stdRes.create = function(callback) + { + callback(); + }; + + stdRes.delete = function(callback) + { + webdav.StandardResource.standardRemoveFromParent(this, callback); + } + + stdRes.moveTo = function(parent, newName, overwrite, callback) + { + webdav.StandardResource.standardMoveTo(this, parent, newName, overwrite, callback); + } + + stdRes.rename = function(newName, callback) + { + const oldname = this.fileName; + this.fileName = newName; + callback(null, oldname, newName); + } + + stdRes.write = function(targetSource, callback) + { + callback(webdav.Errors.InvalidOperation); + } + + stdRes.read = function(targetSource, callback) + { + callback(null, this.openReadStream()); + } + + stdRes.mimeType = function(targetSource, callback) + { + webdav.StandardResource.standardMimeType(this, targetSource, callback); + } + + stdRes.size = function(targetSource, callback) + { + if(this.len >= 0 && Date.now() - this.lenUpdateTime < this.refreshTimeoutMs) + { + callback(null, this.len); + return; + } + + const stream = this.openReadStream(); + stream.on('end', () => callback(null, this.len)); + } + + stdRes.webName = function(callback) + { + callback(null, this.fileName); + } + + stdRes.type = function(callback) + { + callback(null, webdav.ResourceType.File); + } + + stdRes.addChild = function(resource, callback) + { + callback(webdav.Errors.InvalidOperation); + } + stdRes.removeChild = function(resource, callback) + { + callback(webdav.Errors.InvalidOperation); + } + stdRes.getChildren = function(callback) + { + callback(webdav.Errors.InvalidOperation); + } + + + return stdRes; +} diff --git a/examples/v1/customWebResource/package.json b/examples/v1/customWebResource/package.json new file mode 100644 index 00000000..a0b4d3bf --- /dev/null +++ b/examples/v1/customWebResource/package.json @@ -0,0 +1,14 @@ +{ + "name": "customwebresource", + "version": "1.0.0", + "description": "Example to show how to make a simple custom resource base on the web. It gets a web content as its own content.", + "main": "index.js", + "scripts": { + "test": "node test.js" + }, + "author": "Adrien Castex ", + "license": "Unlicense", + "dependencies": { + "webdav-server": "^1.8.2" + } +} diff --git a/examples/v1/customWebResource/test.js b/examples/v1/customWebResource/test.js new file mode 100644 index 00000000..f2c4d63b --- /dev/null +++ b/examples/v1/customWebResource/test.js @@ -0,0 +1,32 @@ +const webFile = require('./js/resource.js'), + webdav = require('webdav-server'); + +new webdav.ResourceTester({ + canHaveVirtualFolderChildren: false, + canHaveVirtualFileChildren: false, + canGetLastModifiedDate: true, + canGetCreationDate: true, + canRemoveChildren: false, + canHaveChildren: false, + canGetChildren: false, + canGetMimeType: true, + canBeCreated: true, + canBeDeleted: true, + canBeRenamed: true, + canGetSize: true, + canBeMoved: true, + canWrite: false, + canRead: true, + canLock: true +}, + // For each battery of tests, create the resource to test + // willCreate : A value of true means you must not call the '.create(...)' method because it will be tested + (willCreate, cb) => cb(new webFile.WebFile('http://unlicense.org/UNLICENSE', 'test.txt')) +).run((results) => { + + // Display the results of the tests + console.log(results.all.isValid ? 'Tests passed!' : 'Tests failed!'); + if(results.all.errors) + for(const value of results.all.errors) + console.log(value.toString()); +}); diff --git a/examples/v1/customWebResource/ts/FSManager.ts b/examples/v1/customWebResource/ts/FSManager.ts new file mode 100644 index 00000000..719c4fe5 --- /dev/null +++ b/examples/v1/customWebResource/ts/FSManager.ts @@ -0,0 +1,33 @@ +import { SerializedObject, FSManager, Errors, IResource, ResourceType } from 'webdav-server' +import { WebFile } from './Resource' + +export class WebFSManager implements FSManager +{ + uid : string = 'WebFSManager_1.0.0'; + + serialize(resource : any, obj : SerializedObject) : object + { + return { + dateCreation: resource.dateCreation, + dateLastModified: resource.dateLastModified, + properties: resource.properties, + webUrl: resource.webUrl, + fileName: resource.fileName, + refreshTimeoutMS: resource.refreshTimeoutMS + }; + } + + unserialize(data : any, obj : SerializedObject) : IResource + { + const rs = new WebFile(data.webUrl, data.fileName, data.refreshTimeoutMS); + rs.dateCreation = data.dateCreation; + rs.dateLastModified = data.dateLastModified; + rs.properties = data.properties; + return rs; + } + + newResource(fullPath : string, name : string, type : ResourceType, parent : IResource) : IResource + { + throw Errors.InvalidOperation; + } +} diff --git a/examples/v1/customWebResource/ts/Resource.ts b/examples/v1/customWebResource/ts/Resource.ts new file mode 100644 index 00000000..2fb02473 --- /dev/null +++ b/examples/v1/customWebResource/ts/Resource.ts @@ -0,0 +1,110 @@ +import * as webFSManager from './FSManager' +import * as request from 'request' +import * as webdav from 'webdav-server' + +export class WebFile extends webdav.StandardResource +{ + fileName : string + webUrl : string + len : number + lenUpdateTime : number + refreshTimeoutMs : number + + constructor(webUrl : string, fileName : string, refreshTimeoutMs : number = 10000) + { + super(null, new webFSManager.WebFSManager()); + + this.refreshTimeoutMs = refreshTimeoutMs; + this.lenUpdateTime = 0; + this.fileName = fileName; + this.webUrl = webUrl; + this.len = -1; + } + + openReadStream() + { + let size = 0; + const stream = request.get(this.webUrl); + stream.on('data', (chunk) => { + size = chunk.length; + }) + stream.on('end', () => { + this.len = size; + this.lenUpdateTime = Date.now(); + }) + stream.end(); + return stream; + } + + create(callback) + { + callback(); + } + + delete(callback) + { + webdav.StandardResource.standardRemoveFromParent(this, callback); + } + + moveTo(parent, newName, overwrite, callback) + { + webdav.StandardResource.standardMoveTo(this, parent, newName, overwrite, callback); + } + + rename(newName, callback) + { + const oldname = this.fileName; + this.fileName = newName; + callback(null, oldname, newName); + } + + write(targetSource, callback) + { + callback(webdav.Errors.InvalidOperation); + } + + read(targetSource, callback) + { + callback(null, this.openReadStream()); + } + + mimeType(targetSource, callback) + { + webdav.StandardResource.standardMimeType(this, targetSource, callback); + } + + size(targetSource, callback) + { + if(this.len >= 0 && Date.now() - this.lenUpdateTime < this.refreshTimeoutMs) + { + callback(null, this.len); + return; + } + + const stream = this.openReadStream(); + stream.on('end', () => callback(null, this.len)); + } + + webName(callback) + { + callback(null, this.fileName); + } + + type(callback) + { + callback(null, webdav.ResourceType.File); + } + + addChild(resource, callback) + { + callback(webdav.Errors.InvalidOperation); + } + removeChild(resource, callback) + { + callback(webdav.Errors.InvalidOperation); + } + getChildren(callback) + { + callback(webdav.Errors.InvalidOperation); + } +} diff --git a/examples/v1/gatewayFTP/README.md b/examples/v1/gatewayFTP/README.md new file mode 100644 index 00000000..301c53a8 --- /dev/null +++ b/examples/v1/gatewayFTP/README.md @@ -0,0 +1,15 @@ +# Custom FTP gateway + +This is an example to show how to use a gateway to manage the content of a remote folder stored on a FTP server. + +The folder `ts` and the folder `js` are the same thing. The `js` folder display the example in JavaScript while the `ts` display the example in TypeScript. + +## Usage + +### Execute + +```bash +node index.js +``` + +But this example needs a fully configured FTP server to connect to. diff --git a/examples/v1/gatewayFTP/index.js b/examples/v1/gatewayFTP/index.js new file mode 100644 index 00000000..c5d5cb7d --- /dev/null +++ b/examples/v1/gatewayFTP/index.js @@ -0,0 +1,47 @@ +const webdav = require('webdav-server'), + ftpFsManager = require('./js/fsManager.js'), + ftpResource = require('./js/ftpGateway.js'); + +const ftpConfig = { + host: '127.0.0.1', + port: 21, + connTimeout: 1000, + pasvTimeout: 1000, + user: 'test' +}; + +const server = new webdav.WebDAVServer({ + port: 1900, + autoSave: { + treeFilePath: './data.json', + tempTreeFilePath: './data.tmp.json' + }, + autoLoad: { + treeFilePath: './data.json', + fsManagers: [ + new webdav.RootFSManager(), + new ftpFsManager.FTPFSManager(ftpConfig), + new webdav.VirtualFSManager() + ] + } +}); + +server.autoLoad((e) => { + if(e) + { + server.addResourceTree(new ftpResource.FTPGateway(ftpConfig, '/', 'ftpGateway'), (e) => { + if(e) throw e; + + run(); + }); + } + else + run(); +}) + +function run() +{ + server.start((s) => { + console.log('Server started on port ' + s.address().port + '.'); + }); +} \ No newline at end of file diff --git a/examples/v1/gatewayFTP/js/fsManager.js b/examples/v1/gatewayFTP/js/fsManager.js new file mode 100644 index 00000000..99f5884c --- /dev/null +++ b/examples/v1/gatewayFTP/js/fsManager.js @@ -0,0 +1,67 @@ +"use strict"; +const webdav = require('webdav-server'), + FTPClient = require('ftp'), + ftpResource = require('./Resource'), + ftpGateway = require('./ftpGateway.js'); + +module.exports.FTPFSManager = function(config) +{ + const fsManager = { + config, + uid: 'FTPFSManager_1.0.0' + config.host + }; + fsManager.constructor = module.exports.FTPFSManager; + + fsManager.connect = function(callback) + { + const client = new FTPClient(); + client.on('ready', () => callback(client)); + client.connect(this.config); + } + + fsManager.serialize = function(resource, obj) + { + if(resource.constructor !== ftpGateway.FTPGateway) + return null; + + return { + dateCreation: resource.dateCreation, + dateLastModified: resource.dateLastModified, + properties: resource.properties, + name: resource.name, + rootPath: resource.rootPath + }; + } + + fsManager.unserialize = function(data, obj) + { + const rs = new ftpGateway.FTPGateway(this.config, data.rootPath, data.name, null, this); + rs.dateCreation = data.dateCreation; + rs.dateLastModified = data.dateLastModified; + rs.properties = data.properties; + return rs; + } + + fsManager.newResource = function(fullPath, name, type, parent) + { + const parentRemotePath = parent.constructor === ftpGateway.FTPGateway ? parent.rootPath : parent .remotePath; + let remotepath; + if(parentRemotePath.lastIndexOf('/') === parentRemotePath.length - 1) + remotepath = parentRemotePath + name; + else + remotepath = parentRemotePath + '/' + name; + + let gateway = parent; + while(gateway && gateway.constructor !== ftpGateway.FTPGateway) + gateway = gateway.parent; + + if(type.isFile) + return new ftpResource.FTPFile(gateway, null, remotepath, parent, this); + else if(type.isDirectory) + return new ftpResource.FTPFolder(gateway, null, remotepath, parent, this); + + throw webdav.Errors.InvalidOperation; + } + + return fsManager; +} diff --git a/examples/v1/gatewayFTP/js/ftpGateway.js b/examples/v1/gatewayFTP/js/ftpGateway.js new file mode 100644 index 00000000..75c3c455 --- /dev/null +++ b/examples/v1/gatewayFTP/js/ftpGateway.js @@ -0,0 +1,175 @@ +"use strict"; +const webdav = require('webdav-server'), + Client = require('ftp'), + ftpResource = require('./Resource'), + ftpFSManager = require('./fsManager.js'); + +module.exports.FTPGateway = function(config, rootPath, fileName, parent, fsManager) +{ + const gateway = new webdav.VirtualFolder(fileName, parent, fsManager ? fsManager : new ftpFSManager.FTPFSManager(config)); + gateway.constructor = module.exports.FTPGateway; + + gateway.rootPath = rootPath ? rootPath : '/'; + gateway.cache = { + '/': gateway + }; + + gateway.listChildren = function(connection, parent, path, callback) + { + if(path.lastIndexOf('/') !== path.length - 1) + path += '/'; + + connection.list(path, (e, list) => { + if(e) + { + callback(e); + return; + } + + callback(null, list.map((file) => { + const resourcePath = path + file.name; + let resource = this.cache[resourcePath]; + if(resource && resource.setInfo) + resource.setInfo(file); + else + { + if(file.type === '-') + resource = new ftpResource.FTPFile(this, file, resourcePath, parent, this.fsManager); + else + resource = new ftpResource.FTPFolder(this, file, resourcePath, parent, this.fsManager); + this.cache[resourcePath] = resource; + } + return resource; + })) + }) + } + + gateway.find = function(connection, path, callback, forceRefresh = false) + { + const resource = this.cache[path.toString()]; + if(forceRefresh || !resource) + { + const parentPath = path.getParent(); + this.find(connection, parentPath, (e, parent) => { + if(e) + { + callback(e); + return; + } + + parent.getChildren((e, actualChildren) => { + if(e) + { + callback(e); + return; + } + + this.listChildren(connection, parent, parentPath.toString(), (e, children) => { + if(e) + { + callback(e); + return; + } + + parent.children.children + .filter((c) => c.constructor !== ftpResource.FTPResource && c.constructor !== ftpResource.FTPFile && c.constructor !== ftpResource.FTPFolder) + .forEach((c) => children.push(c)); + + parent.children.children = children; + + new webdav.Workflow() + .each(children, (child, cb) => { + child.webName((e, name) => { + cb(e, !e && name === path.fileName() ? child : null); + }) + }) + .error(callback) + .done((matchingChildren) => { + for(const child of matchingChildren) + if(child) + { + callback(null, child); + return; + } + + callback(webdav.Errors.ResourceNotFound); + }) + }) + }) + }) + } + else + callback(null, resource); + } + + gateway.refresh = function(requesterPath, callback) + { + const fsPath = new webdav.FSPath(requesterPath); + + this.fsManager.connect((c) => { + this.find(c, fsPath, (e) => { + c.end(); + callback(e); + }, true) + }) + } + + gateway.gateway = function(arg, path, callback) + { + const updateChildren = (c, r, cb) => + { + this.listChildren(c, r, path.toString(), (e, children) => { + if(!e) + { + r.children.children + .filter((c) => c.constructor !== ftpResource.FTPResource && c.constructor !== ftpResource.FTPFile && c.constructor !== ftpResource.FTPFolder) + .forEach((c) => children.push(c)); + + r.children.children = children; + } + + c.end(); + cb(e); + }) + } + + this.fsManager.connect((c) => { + if(path.isRoot()) + { + updateChildren(c, this, (e) => { + callback(e, this); + }) + return; + } + + this.find(c, path, (e, r) => { + if(e) + { + callback(e); + return; + } + + r.type((e, type) => { + if(e) + { + callback(e); + return; + } + + if(type.isFile) + { + c.end(); + callback(e, r); + return; + } + + updateChildren(c, r, (e) => { + callback(e, r); + }) + }) + }); + }); + } + + return gateway; +} diff --git a/examples/v1/gatewayFTP/js/resource.js b/examples/v1/gatewayFTP/js/resource.js new file mode 100644 index 00000000..55e2edf5 --- /dev/null +++ b/examples/v1/gatewayFTP/js/resource.js @@ -0,0 +1,292 @@ +"use strict"; +const webdav = require('webdav-server'), + Transform = require('stream').Transform, + Client = require('ftp'), + ftpGateway = require('./ftpGateway.js'), + ftpFSManager = require('./fsManager.js'); + +module.exports.FTPResource = function(ftpGateway, info, remotePath, parent, fsManager) +{ + const stdRes = new webdav.StandardResource(parent, fsManager); + stdRes.constructor = module.exports.FTPResource; + + stdRes.ftpGateway = ftpGateway; + stdRes.info = info; + stdRes.remotePath = remotePath; + stdRes.lastUpdate = Date.now(); + stdRes.children = new webdav.ResourceChildren(); + + stdRes.setInfo = function(info) + { + this.info = info; + this.lastUpdate = Date.now(); + } + stdRes.refreshInfo = function() + { + this.lastUpdate = 0; + } + stdRes.getInfo = function(callback) + { + const now = Date.now(); + if(now - this.lastUpdate > 1000) + this.ftpGateway.refresh(this.remotePath, (e) => { + this.lastUpdate = now; + callback(e, this.info); + }) + else + callback(null, this.info); + } + stdRes.connect = function(callback) + { + this.fsManager.connect((c) => { + callback(null, c); + }) + } + + // Overwritten by FTPFile and FTPFolder + stdRes.create = function(callback) + { + callback(webdav.Errors.InvalidOperation); + } + + stdRes.moveTo = function(parent, newName, overwrite, callback) + { + if(this.fsManager && parent.fsManager && this.fsManager.uid === parent.fsManager.uid) + { + const newRemotePath = (parent.remotePath ? parent.remotePath : '') + '/' + newName; + this.read(true, (e, rStream) => { + this.connect((e, c) => { + if(e) + { + callback(e); + return; + } + + c.put(rStream, newRemotePath, (e) => { + if(e) + { + callback(e); + return; + } + + c.delete(this.remotePath, (e) => { + if(!e) + { + delete this.ftpGateway.cache[this.remotePath]; + this.remotePath = newRemotePath; + this.ftpGateway.cache[this.remotePath] = this; + } + + this.refreshInfo(); + callback(e); + }) + }) + }) + }) + } + else + callback(webdav.Errors.InvalidOperation); + } + + stdRes.rename = function(newName, callback) + { + this.getInfo((e, info) => { + if(e) + { + callback(e, null, null); + return; + } + + this.connect((e, c) => { + c.rename(info.name, newName, (e) => { + this.refreshInfo(); + this.updateLastModified(); + c.end(); + + const newRemotePath = new webdav.FSPath(this.remotePath).getParent().getChildPath(newName).toString(); + delete this.ftpGateway.cache[this.remotePath]; + this.remotePath = newRemotePath; + this.ftpGateway.cache[this.remotePath] = this; + + callback(e, info.name, newName); + }); + }) + }) + } + + stdRes.write = function(targetSource, callback) + { + this.connect((e, c) => { + if(e) + { + callback(e, null); + return; + } + + const wStream = new Transform({ + transform(chunk, encoding, cb) + { + cb(null, chunk); + } + }); + c.put(wStream, this.remotePath, (e) => { + this.refreshInfo(); + this.updateLastModified(); + c.end(); + }); + callback(null, wStream); + }) + } + + stdRes.read = function(targetSource, callback) + { + this.connect((e, c) => { + if(e) + { + callback(e, null); + return; + } + + c.get(this.remotePath, (e, rStream) => { + if(e) + { + callback(e, null); + return; + } + + const stream = new Transform({ + transform(chunk, encoding, cb) + { + cb(null, chunk); + } + }); + stream.on('finish', () => { + c.end(); + }) + rStream.pipe(stream); + callback(null, stream); + }); + }) + } + + stdRes.mimeType = function(targetSource, callback) + { + webdav.StandardResource.standardMimeType(this, targetSource, callback); + } + + stdRes.size = function(targetSource, callback) + { + this.getInfo((e, info) => { + callback(e, e ? null : parseInt(info.size, 10)); + }) + } + + stdRes.addChild = function(resource, callback) + { + this.children.add(resource, (e) => { + if(!e) + resource.parent = this; + callback(e); + }); + } + + stdRes.removeChild = function(resource, callback) + { + this.children.remove(resource, (e) => { + if(!e) + resource.parent = null; + callback(e); + }); + } + + stdRes.getChildren = function(callback) + { + callback(null, this.children.children); + } + + stdRes.webName = function(callback) + { + this.getInfo((e, info) => { + callback(e, e ? null : info.name); + }) + } + + return stdRes; +} + +module.exports.FTPFile = function(ftpGateway, info, remotePath, parent, fsManager) +{ + const resource = new module.exports.FTPResource(ftpGateway, info, remotePath, parent, fsManager); + resource.constructor = module.exports.FTPFile; + + resource.create = function(callback) + { + this.write(true, (e, w) => { + if(e) + callback(e); + else + w.end(new Buffer(0), callback); + }) + } + + resource.type = function(callback) + { + callback(null, webdav.ResourceType.File); + } + + resource.delete = function(callback) + { + this.connect((e, c) => { + c.delete(this.remotePath, (e) => { + this.refreshInfo(); + c.end(); + + if(!e) + webdav.StandardResource.standardRemoveFromParent(this, callback); + else + callback(e); + }) + }) + } + + return resource; +} + +module.exports.FTPFolder = function(ftpGateway, info, remotePath, parent, fsManager) +{ + const resource = new module.exports.FTPResource(ftpGateway, info, remotePath, parent, fsManager); + resource.constructor = module.exports.FTPFolder; + + resource.create = function(callback) + { + this.connect((e, c) => { + c.mkdir(this.remotePath, false, (e) => { + c.end(); + this.refreshInfo(); + callback(e); + }); + }) + } + + resource.type = function(callback) + { + callback(null, webdav.ResourceType.Directory); + } + + resource.delete = function(callback) + { + this.connect((e, c) => { + c.rmdir(this.remotePath, (e) => { + this.refreshInfo(); + c.end(); + + if(!e) + webdav.StandardResource.standardRemoveFromParent(this, callback); + else + callback(e); + }) + }) + } + + return resource; +} diff --git a/examples/v1/gatewayFTP/package.json b/examples/v1/gatewayFTP/package.json new file mode 100644 index 00000000..f46c3ff3 --- /dev/null +++ b/examples/v1/gatewayFTP/package.json @@ -0,0 +1,15 @@ +{ + "name": "customftpgateway", + "version": "1.0.0", + "description": "Example to show how to use a gateway to manage the content of a remote folder stored on a FTP server.", + "main": "index.js", + "author": "Adrien Castex ", + "license": "Unlicense", + "dependencies": { + "ftp": "^0.3.10", + "webdav-server": "^1.9.0" + }, + "devDependencies": { + "@types/ftp": "^0.3.29" + } +} diff --git a/examples/v1/gatewayFTP/ts/FSManager.ts b/examples/v1/gatewayFTP/ts/FSManager.ts new file mode 100644 index 00000000..95e1cb53 --- /dev/null +++ b/examples/v1/gatewayFTP/ts/FSManager.ts @@ -0,0 +1,64 @@ +import { SerializedObject, FSManager, Errors, IResource, ResourceType } from 'webdav-server' +import { FTPFile, FTPFolder, FTPResource, } from './Resource' +import { FTPGateway } from './FTPGateway' +import * as FTPClient from 'ftp' + +export class FTPFSManager implements FSManager +{ + uid : string; + + connect(callback : (client : FTPClient) => void) + { + const client = new FTPClient(); + client.on('ready', () => callback(client)); + client.connect(this.config); + } + + constructor(public config : FTPClient.Options) + { + this.uid = 'FTPFSManager_1.0.0' + config.host; + } + + serialize(resource : any, obj : SerializedObject) : object + { + if(resource.constructor !== FTPGateway) + return null; + + return { + dateCreation: resource.dateCreation, + dateLastModified: resource.dateLastModified, + properties: resource.properties, + name: resource.name, + rootPath: resource.rootPath + }; + } + unserialize(data : any, obj : SerializedObject) : IResource + { + const rs = new FTPGateway(this.config, data.rootPath, data.name, null, this); + rs.dateCreation = data.dateCreation; + rs.dateLastModified = data.dateLastModified; + rs.properties = data.properties; + return rs; + } + + newResource(fullPath : string, name : string, type : ResourceType, parent : IResource) : IResource + { + const parentRemotePath = parent.constructor === FTPGateway ? (parent as FTPGateway).rootPath : (parent as FTPResource).remotePath; + let remotepath; + if(parentRemotePath.lastIndexOf('/') === parentRemotePath.length - 1) + remotepath = parentRemotePath + name; + else + remotepath = parentRemotePath + '/' + name; + + let gateway = parent; + while(gateway && gateway.constructor !== FTPGateway) + gateway = gateway.parent; + + if(type.isFile) + return new FTPFile(gateway as FTPGateway, null, remotepath, parent, this); + else if(type.isDirectory) + return new FTPFolder(gateway as FTPGateway, null, remotepath, parent, this); + + throw Errors.InvalidOperation; + } +} diff --git a/examples/v1/gatewayFTP/ts/FTPGateway.ts b/examples/v1/gatewayFTP/ts/FTPGateway.ts new file mode 100644 index 00000000..96d3b4ff --- /dev/null +++ b/examples/v1/gatewayFTP/ts/FTPGateway.ts @@ -0,0 +1,177 @@ +import * as ftpFSManager from './FSManager' +import * as webdav from 'webdav-server' +import * as Client from 'ftp' +import { FTPFile, FTPFolder, FTPResource } from './Resource' + +export class FTPGateway extends webdav.VirtualFolder +{ + rootPath : string + cache : any + + constructor(config : Client.Options, rootPath : string, fileName : string, parent ?: webdav.IResource, fsManager ?: webdav.FSManager) + { + super(fileName, parent, fsManager ? fsManager : new ftpFSManager.FTPFSManager(config)); + + this.rootPath = rootPath ? rootPath : '/'; + this.cache = { + '/': this + }; + } + + protected listChildren(connection : Client, parent : webdav.IResource, path : string, callback : (error : Error, children ?: FTPResource[]) => void) + { + if(path.lastIndexOf('/') !== path.length - 1) + path += '/'; + + connection.list(path, (e, list) => { + if(e) + { + callback(e); + return; + } + + callback(null, list.map((file) => { + const resourcePath = path + file.name; + let resource = this.cache[resourcePath]; + if(resource && resource.setInfo) + resource.setInfo(file); + else + { + if(file.type === '-') + resource = new FTPFile(this, file, resourcePath, parent, this.fsManager); + else + resource = new FTPFolder(this, file, resourcePath, parent, this.fsManager); + this.cache[resourcePath] = resource; + } + return resource; + })) + }) + } + + protected find(connection : Client, path : webdav.FSPath, callback : (error : Error, resource ?: FTPResource) => void, forceRefresh : boolean = false) + { + const resource = this.cache[path.toString()]; + if(forceRefresh || !resource) + { + const parentPath = path.getParent(); + this.find(connection, parentPath, (e, parent) => { + if(e) + { + callback(e); + return; + } + + parent.getChildren((e, actualChildren) => { + if(e) + { + callback(e); + return; + } + + this.listChildren(connection, parent, parentPath.toString(), (e, children) => { + if(e) + { + callback(e); + return; + } + + parent.children.children + .filter((c) => c.constructor !== FTPResource && c.constructor !== FTPFile && c.constructor !== FTPFolder) + .forEach((c) => (children as webdav.IResource[]).push(c)); + + parent.children.children = children; + + new webdav.Workflow() + .each(children, (child, cb) => { + child.webName((e, name) => { + cb(e, !e && name === path.fileName() ? child : null); + }) + }) + .error(callback) + .done((matchingChildren) => { + for(const child of matchingChildren) + if(child) + { + callback(null, child); + return; + } + + callback(webdav.Errors.ResourceNotFound); + }) + }) + }) + }) + } + else + callback(null, resource); + } + + refresh(requesterPath : string, callback : (error : Error) => void) + { + const fsPath = new webdav.FSPath(requesterPath); + + (this.fsManager as ftpFSManager.FTPFSManager).connect((c) => { + this.find(c, fsPath, (e) => { + c.end(); + callback(e); + }, true) + }) + } + + gateway(arg : webdav.MethodCallArgs, path : webdav.FSPath, callback : (error : Error, resource ?: webdav.IResource) => void) + { + const updateChildren = (c, r, cb) => + { + this.listChildren(c, r, path.toString(), (e, children) => { + if(!e) + { + r.children.children + .filter((c) => c.constructor !== FTPResource && c.constructor !== FTPFile && c.constructor !== FTPFolder) + .forEach((c) => (children as webdav.IResource[]).push(c)); + + r.children.children = children; + } + + c.end(); + cb(e); + }) + } + + (this.fsManager as ftpFSManager.FTPFSManager).connect((c) => { + if(path.isRoot()) + { + updateChildren(c, this, (e) => { + callback(e, this); + }) + return; + } + + this.find(c, path, (e, r) => { + if(e) + { + callback(e); + return; + } + + r.type((e, type) => { + if(e) + { + callback(e); + return; + } + + if(type.isFile) + { + c.end(); + callback(e, r); + return; + } + + updateChildren(c, r, (e) => { + callback(e, r); + }) + }) + }); + }); + } +} diff --git a/examples/v1/gatewayFTP/ts/Resource.ts b/examples/v1/gatewayFTP/ts/Resource.ts new file mode 100644 index 00000000..fe8e76e5 --- /dev/null +++ b/examples/v1/gatewayFTP/ts/Resource.ts @@ -0,0 +1,285 @@ +import { Readable, Writable, Transform } from 'stream' +import { FTPGateway } from './FTPGateway' +import * as ftpFSManager from './FSManager' +import * as webdav from 'webdav-server' +import * as Client from 'ftp' + +export abstract class FTPResource extends webdav.StandardResource +{ + children : webdav.ResourceChildren + lastUpdate : number + + constructor(public ftpGateway : FTPGateway, public info : Client.ListingElement, public remotePath : string, parent : webdav.IResource, fsManager : webdav.FSManager) + { + super(parent, fsManager); + + this.lastUpdate = Date.now(); + this.children = new webdav.ResourceChildren(); + } + + setInfo(info : Client.ListingElement) + { + this.info = info; + this.lastUpdate = Date.now(); + } + refreshInfo() + { + this.lastUpdate = 0; + } + getInfo(callback : webdav.ReturnCallback) + { + const now = Date.now(); + if(now - this.lastUpdate > 1000) + this.ftpGateway.refresh(this.remotePath, (e) => { + this.lastUpdate = now; + callback(e, this.info); + }) + else + callback(null, this.info); + } + connect(callback : webdav.ReturnCallback) + { + (this.fsManager as ftpFSManager.FTPFSManager).connect((c) => { + callback(null, c); + }) + } + + // Overwritten by FTPFile and FTPFolder + create(callback: webdav.SimpleCallback) + { + callback(webdav.Errors.InvalidOperation); + } + + abstract delete(callback : webdav.SimpleCallback); + + moveTo(parent : webdav.IResource, newName : string, overwrite : boolean, callback : webdav.SimpleCallback) + { + if(this.fsManager && parent.fsManager && this.fsManager.uid === parent.fsManager.uid) + { + const newRemotePath = ((parent as any).remotePath ? (parent as any).remotePath : '') + '/' + newName; + this.read(true, (e, rStream) => { + this.connect((e, c) => { + if(e) + { + callback(e); + return; + } + + c.put(rStream, newRemotePath, (e) => { + if(e) + { + callback(e); + return; + } + + c.delete(this.remotePath, (e) => { + if(!e) + { + delete this.ftpGateway.cache[this.remotePath]; + this.remotePath = newRemotePath; + this.ftpGateway.cache[this.remotePath] = this; + } + + this.refreshInfo(); + callback(e); + }) + }) + }) + }) + } + else + callback(webdav.Errors.InvalidOperation); + } + + rename(newName : string, callback : webdav.Return2Callback) + { + this.getInfo((e, info) => { + if(e) + { + callback(e, null, null); + return; + } + + this.connect((e, c) => { + c.rename(info.name, newName, (e) => { + this.refreshInfo(); + this.updateLastModified(); + c.end(); + + const newRemotePath = new webdav.FSPath(this.remotePath).getParent().getChildPath(newName).toString(); + delete this.ftpGateway.cache[this.remotePath]; + this.remotePath = newRemotePath; + this.ftpGateway.cache[this.remotePath] = this; + + callback(e, info.name, newName); + }); + }) + }) + } + + write(targetSource : boolean, callback : webdav.ReturnCallback) + { + this.connect((e, c) => { + if(e) + { + callback(e, null); + return; + } + + const wStream = new Transform({ + transform(chunk, encoding, cb) + { + cb(null, chunk); + } + }); + c.put(wStream, this.remotePath, (e) => { + this.refreshInfo(); + this.updateLastModified(); + c.end(); + }); + callback(null, wStream); + }) + } + + read(targetSource : boolean, callback : webdav.ReturnCallback) + { + this.connect((e, c) => { + if(e) + { + callback(e, null); + return; + } + + c.get(this.remotePath, (e, rStream) => { + if(e) + { + callback(e, null); + return; + } + + const stream = new Transform({ + transform(chunk, encoding, cb) + { + cb(null, chunk); + } + }); + stream.on('finish', () => { + c.end(); + }) + rStream.pipe(stream); + callback(null, stream); + }); + }) + } + + mimeType(targetSource : boolean, callback : webdav.ReturnCallback) + { + webdav.StandardResource.standardMimeType(this, targetSource, callback); + } + + size(targetSource : boolean, callback : webdav.ReturnCallback) + { + this.getInfo((e, info) => { + callback(e, e ? null : parseInt(info.size, 10)); + }) + } + + addChild(resource : webdav.IResource, callback : webdav.SimpleCallback) + { + this.children.add(resource, (e) => { + if(!e) + resource.parent = this; + callback(e); + }); + } + + removeChild(resource : webdav.IResource, callback : webdav.SimpleCallback) + { + this.children.remove(resource, (e) => { + if(!e) + resource.parent = null; + callback(e); + }); + } + + getChildren(callback: webdav.ReturnCallback) + { + callback(null, this.children.children); + } + + webName(callback : webdav.ReturnCallback) + { + this.getInfo((e, info) => { + callback(e, e ? null : info.name); + }) + } + + abstract type(callback : webdav.ReturnCallback); +} + +export class FTPFile extends FTPResource +{ + create(callback: webdav.SimpleCallback) + { + this.write(true, (e, w) => { + if(e) + callback(e); + else + w.end(new Buffer(0), callback); + }) + } + + type(callback : webdav.ReturnCallback) + { + callback(null, webdav.ResourceType.File); + } + + delete(callback : webdav.SimpleCallback) + { + this.connect((e, c) => { + c.delete(this.remotePath, (e) => { + this.refreshInfo(); + c.end(); + + if(!e) + webdav.StandardResource.standardRemoveFromParent(this, callback); + else + callback(e); + }) + }) + } +} + +export class FTPFolder extends FTPResource +{ + create(callback: webdav.SimpleCallback) + { + this.connect((e, c) => { + c.mkdir(this.remotePath, false, (e) => { + c.end(); + this.refreshInfo(); + callback(e); + }); + }) + } + + type(callback : webdav.ReturnCallback) + { + callback(null, webdav.ResourceType.Directory); + } + + delete(callback : webdav.SimpleCallback) + { + this.connect((e, c) => { + c.rmdir(this.remotePath, (e) => { + this.refreshInfo(); + c.end(); + + if(!e) + webdav.StandardResource.standardRemoveFromParent(this, callback); + else + callback(e); + }) + }) + } +} diff --git a/examples/v1/gatewayPhysical/README.md b/examples/v1/gatewayPhysical/README.md new file mode 100644 index 00000000..c91bd4fa --- /dev/null +++ b/examples/v1/gatewayPhysical/README.md @@ -0,0 +1,13 @@ +# Custom Physical gateway + +This is an example to show how to use a gateway to manage the content of a physical folder. + +The folder `ts` and the folder `js` are the same thing. The `js` folder display the example in JavaScript while the `ts` display the example in TypeScript. + +## Usage + +### Execute + +```bash +node index.js +``` diff --git a/examples/v1/gatewayPhysical/index.js b/examples/v1/gatewayPhysical/index.js new file mode 100644 index 00000000..f7695e29 --- /dev/null +++ b/examples/v1/gatewayPhysical/index.js @@ -0,0 +1,39 @@ +const webdav = require('webdav-server'), + phyFsManager = require('./js/PhysicalGFSManager.js'), + gateway = require('./js/PhysicalGateway.js'); + +const server = new webdav.WebDAVServer({ + port: 1900, + autoSave: { + treeFilePath: './data.json', + tempTreeFilePath: './data.tmp.json' + }, + autoLoad: { + treeFilePath: './data.json', + fsManagers: [ + new webdav.RootFSManager(), + new phyFsManager.PhysicalGFSManager(), + new webdav.VirtualFSManager() + ] + } +}); + +server.autoLoad((e) => { + if(e) + { + server.addResourceTree(new gateway.PhysicalGateway('./testData', 'phyGateway'), (e) => { + if(e) throw e; + + run(); + }); + } + else + run(); +}) + +function run() +{ + server.start((s) => { + console.log('Server started on port ' + s.address().port + '.'); + }); +} \ No newline at end of file diff --git a/examples/v1/gatewayPhysical/js/PhysicalGFSManager.js b/examples/v1/gatewayPhysical/js/PhysicalGFSManager.js new file mode 100644 index 00000000..ea4e891c --- /dev/null +++ b/examples/v1/gatewayPhysical/js/PhysicalGFSManager.js @@ -0,0 +1,39 @@ +"use strict"; +const webdav = require('webdav-server'), + FTPClient = require('ftp'), + physicalGateway = require('./PhysicalGateway.js'); + +module.exports.PhysicalGFSManager = function() +{ + const fsManager = new webdav.PhysicalFSManager(); + fsManager.constructor = module.exports.PhysicalGFSManager; + + fsManager.uid = "PhysicalGFSManager_1.0.0"; + + fsManager.serialize = function(resource, obj) + { + if(resource.constructor !== physicalGateway.PhysicalGateway) + return null; + + return { + realPath: resource.realPath, + dateCreation: resource.dateCreation, + dateLastModified: resource.dateLastModified, + properties: resource.properties, + customName: resource.customName + }; + } + + fsManager.unserialize = function(data, obj) + { + const rs = new physicalGateway.PhysicalGateway(data.realPath, data.customName, null, this); + + rs.dateCreation = data.dateCreation; + rs.dateLastModified = data.dateLastModified; + rs.properties = data.properties; + + return rs; + } + + return fsManager; +} diff --git a/examples/v1/gatewayPhysical/js/PhysicalGateway.js b/examples/v1/gatewayPhysical/js/PhysicalGateway.js new file mode 100644 index 00000000..c3622176 --- /dev/null +++ b/examples/v1/gatewayPhysical/js/PhysicalGateway.js @@ -0,0 +1,183 @@ +"use strict"; +const webdav = require('webdav-server'), + physicalGFSManager = require('./PhysicalGFSManager.js'), + path = require('path'), + fs = require('fs'); + +module.exports.PhysicalGateway = function(rootPath, customName, parent, fsManager) +{ + const gateway = new webdav.PhysicalFolder(rootPath, parent, fsManager ? fsManager : new physicalGFSManager.PhysicalGFSManager()); + gateway.constructor = module.exports.PhysicalGateway; + + gateway.customName = customName; + gateway.rootPath = rootPath ? rootPath : '/'; + gateway.cache = { + '/': gateway + }; + + const super_webName = gateway.webName; + gateway.webName = function(callback) + { + if(this.customName) + callback(null, this.customName); + else + super_webName(callback); + } + + gateway.listChildren = function(parent, rpath, callback) + { + if(rpath.lastIndexOf('/') !== rpath.length - 1) + rpath += '/'; + + fs.readdir(parent.realPath, (e, list) => { + if(e) + { + callback(e); + return; + } + + new webdav.Workflow() + .each(list, (file, cb) => { + const resourcePath = rpath + file; + let resource = this.cache[resourcePath]; + const realPath = path.join(parent.realPath, file); + + if(resource) + { + cb(null, resource); + return; + } + + fs.stat(realPath, (e, stat) => { + if(e) + { + cb(e); + return; + } + + if(stat.isFile()) + resource = new webdav.PhysicalFile(realPath, parent, this.fsManager); + else + resource = new webdav.PhysicalFolder(realPath, parent, this.fsManager); + resource.deleteOnMoved = true; + this.cache[resourcePath] = resource; + cb(null, resource); + }) + }) + .error(callback) + .done((resources) => callback(null, resources)); + }) + } + + gateway.find = function(path, callback, forceRefresh = false) + { + const resource = this.cache[path.toString()]; + if(forceRefresh || !resource) + { + const parentPath = path.getParent(); + this.find(parentPath, (e, parent) => { + if(e) + { + callback(e); + return; + } + + parent.getChildren((e, actualChildren) => { + if(e) + { + callback(e); + return; + } + + this.listChildren(parent, parentPath.toString(), (e, children) => { + if(e) + { + callback(e); + return; + } + + actualChildren + .filter((c) => c.constructor !== webdav.PhysicalResource && c.constructor !== webdav.PhysicalFile && c.constructor !== webdav.PhysicalFolder) + .forEach((c) => children.push(c)); + + parent.children.children = children; + + new webdav.Workflow() + .each(children, (child, cb) => { + child.webName((e, name) => { + cb(e, !e && name === path.fileName() ? child : null); + }) + }) + .error(callback) + .done((matchingChildren) => { + for(const child of matchingChildren) + if(child) + { + callback(null, child); + return; + } + + callback(webdav.Errors.ResourceNotFound); + }) + }) + }) + }) + } + else + callback(null, resource); + } + + gateway.gateway = function(arg, path, callback) + { + const updateChildren = (r, cb) => + { + this.listChildren(r, path.toString(), (e, children) => { + if(!e) + { + r.children.children + .filter((c) => c.constructor !== webdav.PhysicalResource && c.constructor !== webdav.PhysicalFile && c.constructor !== webdav.PhysicalFolder) + .forEach((c) => children.push(c)); + + r.children.children = children; + } + cb(e); + }) + } + + if(path.isRoot()) + { + updateChildren(this, (e) => { + callback(e, this); + }) + return; + } + + this.find(path, (e, r) => { + if(e) + { + callback(e); + return; + } + + r.type((e, type) => { + if(e) + { + callback(e); + return; + } + + if(type.isFile) + { + callback(e, r); + return; + } + + updateChildren(r, (e) => { + callback(e, r); + }) + }) + }); + } + + return gateway; +} diff --git a/examples/v1/gatewayPhysical/package.json b/examples/v1/gatewayPhysical/package.json new file mode 100644 index 00000000..97051ca1 --- /dev/null +++ b/examples/v1/gatewayPhysical/package.json @@ -0,0 +1,11 @@ +{ + "name": "customphysicalgateway", + "version": "1.0.0", + "description": "Example to show how to use a gateway to manage the content of a physical folder.", + "main": "index.js", + "author": "Adrien Castex ", + "license": "Unlicense", + "dependencies": { + "webdav-server": "^1.9.0" + } +} diff --git a/examples/v1/gatewayPhysical/testData/a.txt b/examples/v1/gatewayPhysical/testData/a.txt new file mode 100644 index 00000000..b5f515de --- /dev/null +++ b/examples/v1/gatewayPhysical/testData/a.txt @@ -0,0 +1 @@ +This is some data 2. \ No newline at end of file diff --git a/examples/v1/gatewayPhysical/testData/fold/j.txt b/examples/v1/gatewayPhysical/testData/fold/j.txt new file mode 100644 index 00000000..57e6d403 --- /dev/null +++ b/examples/v1/gatewayPhysical/testData/fold/j.txt @@ -0,0 +1 @@ +This is some data 3. \ No newline at end of file diff --git a/examples/v1/gatewayPhysical/testData/x.txt b/examples/v1/gatewayPhysical/testData/x.txt new file mode 100644 index 00000000..358ed19b --- /dev/null +++ b/examples/v1/gatewayPhysical/testData/x.txt @@ -0,0 +1 @@ +This is some data. \ No newline at end of file diff --git a/examples/v1/gatewayPhysical/ts/PhysicalGFSManager.ts b/examples/v1/gatewayPhysical/ts/PhysicalGFSManager.ts new file mode 100644 index 00000000..d6980105 --- /dev/null +++ b/examples/v1/gatewayPhysical/ts/PhysicalGFSManager.ts @@ -0,0 +1,31 @@ +import { SerializedObject, FSManager, Errors, IResource, ResourceType, PhysicalFSManager, PhysicalFile, PhysicalFolder, PhysicalResource } from '../../../lib/index.js' +import { PhysicalGateway } from './PhysicalGateway' + +export class PhysicalGFSManager extends PhysicalFSManager +{ + uid : string = "PhysicalGFSManager_1.0.0"; + + serialize(resource : any, obj : SerializedObject) : object + { + if(resource.constructor !== PhysicalGateway) + return null; + + return { + realPath: resource.realPath, + dateCreation: resource.dateCreation, + dateLastModified: resource.dateLastModified, + properties: resource.properties, + customName: resource.customName + }; + } + unserialize(data : any, obj : SerializedObject) : IResource + { + const rs = new PhysicalGateway(data.realPath, data.customName, null, this); + + rs.dateCreation = data.dateCreation; + rs.dateLastModified = data.dateLastModified; + rs.properties = data.properties; + + return rs; + } +} diff --git a/examples/v1/gatewayPhysical/ts/PhysicalGateway.ts b/examples/v1/gatewayPhysical/ts/PhysicalGateway.ts new file mode 100644 index 00000000..ee5ec7a0 --- /dev/null +++ b/examples/v1/gatewayPhysical/ts/PhysicalGateway.ts @@ -0,0 +1,183 @@ +import { PhysicalGFSManager } from './PhysicalGFSManager' +import * as webdav from '../../../lib/index.js' +import * as path from 'path' +import * as fs from 'fs' + +export class PhysicalGateway extends webdav.PhysicalFolder +{ + cache : { + [path : string] : webdav.PhysicalResource + } + + constructor(rootPath : string, protected customName ?: string, parent ?: webdav.IResource, fsManager ?: webdav.FSManager) + { + super(rootPath, parent, fsManager ? fsManager : new PhysicalGFSManager()); + + this.cache = { + '/': this + }; + } + + webName(callback : webdav.ReturnCallback) + { + if(this.customName) + callback(null, this.customName); + else + super.webName(callback); + } + + protected listChildren(parent : webdav.PhysicalResource, rpath : string, callback : (error : Error, children ?: webdav.IResource[]) => void) + { + if(rpath.lastIndexOf('/') !== rpath.length - 1) + rpath += '/'; + + fs.readdir(parent.realPath, (e, list) => { + if(e) + { + callback(e); + return; + } + + new webdav.Workflow() + .each(list, (file, cb) => { + const resourcePath = rpath + file; + let resource = this.cache[resourcePath]; + const realPath = path.join(parent.realPath, file); + + if(resource) + { + cb(null, resource); + return; + } + + fs.stat(realPath, (e, stat) => { + if(e) + { + cb(e); + return; + } + + if(stat.isFile()) + resource = new webdav.PhysicalFile(realPath, parent, this.fsManager); + else + resource = new webdav.PhysicalFolder(realPath, parent, this.fsManager); + (resource as webdav.PhysicalResource).deleteOnMoved = true; + this.cache[resourcePath] = resource; + cb(null, resource); + }) + }) + .error(callback) + .done((resources) => callback(null, resources)); + }) + } + + protected find(path : webdav.FSPath, callback : (error : Error, resource ?: webdav.PhysicalResource) => void, forceRefresh : boolean = false) + { + const resource = this.cache[path.toString()]; + if(forceRefresh || !resource) + { + const parentPath = path.getParent(); + this.find(parentPath, (e, parent) => { + if(e) + { + callback(e); + return; + } + + parent.getChildren((e, actualChildren) => { + if(e) + { + callback(e); + return; + } + + this.listChildren(parent, parentPath.toString(), (e, children) => { + if(e) + { + callback(e); + return; + } + + actualChildren + .filter((c) => c.constructor !== webdav.PhysicalResource && c.constructor !== webdav.PhysicalFile && c.constructor !== webdav.PhysicalFolder) + .forEach((c) => children.push(c)); + + (parent as webdav.PhysicalFolder).children.children = children; + + new webdav.Workflow() + .each(children, (child, cb) => { + child.webName((e, name) => { + cb(e, !e && name === path.fileName() ? child : null); + }) + }) + .error(callback) + .done((matchingChildren) => { + for(const child of matchingChildren) + if(child) + { + callback(null, child); + return; + } + + callback(webdav.Errors.ResourceNotFound); + }) + }) + }) + }) + } + else + callback(null, resource); + } + + gateway(arg : webdav.MethodCallArgs, path : webdav.FSPath, callback : (error : Error, resource ?: webdav.IResource) => void) + { + const updateChildren = (r, cb) => + { + this.listChildren(r, path.toString(), (e, children) => { + if(!e) + { + (r as webdav.PhysicalFolder).children.children + .filter((c) => c.constructor !== webdav.PhysicalResource && c.constructor !== webdav.PhysicalFile && c.constructor !== webdav.PhysicalFolder) + .forEach((c) => (children as webdav.IResource[]).push(c)); + + (r as webdav.PhysicalFolder).children.children = children; + } + cb(e); + }) + } + + if(path.isRoot()) + { + updateChildren(this, (e) => { + callback(e, this); + }) + return; + } + + this.find(path, (e, r) => { + if(e) + { + callback(e); + return; + } + + r.type((e, type) => { + if(e) + { + callback(e); + return; + } + + if(type.isFile) + { + callback(e, r); + return; + } + + updateChildren(r, (e) => { + callback(e, r); + }) + }) + }); + } +} diff --git a/examples/v1/loadPhysicalFolder/README.md b/examples/v1/loadPhysicalFolder/README.md new file mode 100644 index 00000000..e7ba3f78 --- /dev/null +++ b/examples/v1/loadPhysicalFolder/README.md @@ -0,0 +1,11 @@ +# Custom Web Resource + +This is an example to show how to load a physical folder. + +## Usage + +### Execute + +```bash +node index.js +``` diff --git a/examples/v1/loadPhysicalFolder/data/a.txt b/examples/v1/loadPhysicalFolder/data/a.txt new file mode 100644 index 00000000..af2986f5 --- /dev/null +++ b/examples/v1/loadPhysicalFolder/data/a.txt @@ -0,0 +1,3 @@ +Hello friends! + +Is it ok? diff --git a/examples/v1/loadPhysicalFolder/data/b/c.txt b/examples/v1/loadPhysicalFolder/data/b/c.txt new file mode 100644 index 00000000..e69de29b diff --git a/examples/v1/loadPhysicalFolder/data/b/e.txt b/examples/v1/loadPhysicalFolder/data/b/e.txt new file mode 100644 index 00000000..e69de29b diff --git a/examples/v1/loadPhysicalFolder/data/b/x/d.txt b/examples/v1/loadPhysicalFolder/data/b/x/d.txt new file mode 100644 index 00000000..e69de29b diff --git a/examples/v1/loadPhysicalFolder/index.js b/examples/v1/loadPhysicalFolder/index.js new file mode 100644 index 00000000..bb00cb54 --- /dev/null +++ b/examples/v1/loadPhysicalFolder/index.js @@ -0,0 +1,17 @@ +const webdav = require('webdav-server'); + +webdav.PhysicalFolder.loadFromPath('./data', (e, folder) => { + if(e) throw e; + + const server = new webdav.WebDAVServer({ + port: 1900 + }); + + server.addResourceTree(folder, (e) => { + if(e) throw e; + + server.start((s) => { + console.log('Server started on port ' + s.address().port + '.'); + }); + }); +}); diff --git a/examples/v1/loadPhysicalFolder/index.ts b/examples/v1/loadPhysicalFolder/index.ts new file mode 100644 index 00000000..3e52965b --- /dev/null +++ b/examples/v1/loadPhysicalFolder/index.ts @@ -0,0 +1,17 @@ +import { PhysicalFolder, WebDAVServer } from 'webdav-server' + +PhysicalFolder.loadFromPath('./data', (e, folder) => { + if(e) throw e; + + const server = new WebDAVServer({ + port: 1900 + }); + + server.addResourceTree(folder, (e) => { + if(e) throw e; + + server.start((s) => { + console.log('Server started on port ' + s.address().port + '.'); + }); + }); +}); diff --git a/examples/v1/loadPhysicalFolder/package.json b/examples/v1/loadPhysicalFolder/package.json new file mode 100644 index 00000000..ecb2d1cd --- /dev/null +++ b/examples/v1/loadPhysicalFolder/package.json @@ -0,0 +1,14 @@ +{ + "name": "loadphysicalfolder", + "version": "1.0.0", + "description": "Example to show how to load a physical folder.", + "main": "index.js", + "scripts": { + "test": "node test.js" + }, + "author": "Adrien Castex ", + "license": "Unlicense", + "dependencies": { + "webdav-server": "^1.8.2" + } +}