From 45f1055ca3cdf44193dd3f091e46313c7331412b Mon Sep 17 00:00:00 2001 From: Adrien Castex Date: Mon, 19 Jun 2017 15:00:54 +0200 Subject: [PATCH] Added an example for the gateway : FTP Gateway --- examples/gatewayFTP/README.md | 15 ++ examples/gatewayFTP/index.js | 47 +++++ examples/gatewayFTP/js/fsManager.js | 67 ++++++ examples/gatewayFTP/js/ftpGateway.js | 175 ++++++++++++++++ examples/gatewayFTP/js/resource.js | 292 +++++++++++++++++++++++++++ examples/gatewayFTP/package.json | 18 ++ examples/gatewayFTP/ts/FSManager.ts | 64 ++++++ examples/gatewayFTP/ts/FTPGateway.ts | 177 ++++++++++++++++ examples/gatewayFTP/ts/Resource.ts | 285 ++++++++++++++++++++++++++ 9 files changed, 1140 insertions(+) create mode 100644 examples/gatewayFTP/README.md create mode 100644 examples/gatewayFTP/index.js create mode 100644 examples/gatewayFTP/js/fsManager.js create mode 100644 examples/gatewayFTP/js/ftpGateway.js create mode 100644 examples/gatewayFTP/js/resource.js create mode 100644 examples/gatewayFTP/package.json create mode 100644 examples/gatewayFTP/ts/FSManager.ts create mode 100644 examples/gatewayFTP/ts/FTPGateway.ts create mode 100644 examples/gatewayFTP/ts/Resource.ts diff --git a/examples/gatewayFTP/README.md b/examples/gatewayFTP/README.md new file mode 100644 index 00000000..301c53a8 --- /dev/null +++ b/examples/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/gatewayFTP/index.js b/examples/gatewayFTP/index.js new file mode 100644 index 00000000..c5d5cb7d --- /dev/null +++ b/examples/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/gatewayFTP/js/fsManager.js b/examples/gatewayFTP/js/fsManager.js new file mode 100644 index 00000000..2ca29f99 --- /dev/null +++ b/examples/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: 'WebDAVFSManager_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/gatewayFTP/js/ftpGateway.js b/examples/gatewayFTP/js/ftpGateway.js new file mode 100644 index 00000000..75c3c455 --- /dev/null +++ b/examples/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/gatewayFTP/js/resource.js b/examples/gatewayFTP/js/resource.js new file mode 100644 index 00000000..55e2edf5 --- /dev/null +++ b/examples/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/gatewayFTP/package.json b/examples/gatewayFTP/package.json new file mode 100644 index 00000000..4f1a744f --- /dev/null +++ b/examples/gatewayFTP/package.json @@ -0,0 +1,18 @@ +{ + "name": "customwebresource", + "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", + "scripts": { + "test": "node test.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/gatewayFTP/ts/FSManager.ts b/examples/gatewayFTP/ts/FSManager.ts new file mode 100644 index 00000000..c6ca0bc5 --- /dev/null +++ b/examples/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 = 'WebDAVFSManager_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/gatewayFTP/ts/FTPGateway.ts b/examples/gatewayFTP/ts/FTPGateway.ts new file mode 100644 index 00000000..96d3b4ff --- /dev/null +++ b/examples/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/gatewayFTP/ts/Resource.ts b/examples/gatewayFTP/ts/Resource.ts new file mode 100644 index 00000000..fe8e76e5 --- /dev/null +++ b/examples/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); + }) + }) + } +}