├── index.js ├── .gitignore ├── package.json ├── lib ├── log.js └── ceph-swift.js └── README.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/ceph-swift.js'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.js 2 | node_modules 3 | test.js 4 | __swift_auth_cache.json 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swift-ceph", 3 | "author": "Ágúst Ingi Kjartansson", 4 | "version": "0.1.1", 5 | "description": "CEPH Object Storage(Swift) REST client API for Node.JS", 6 | "homepage": "http://github.com/edge-is/swift-ceph", 7 | "keywords": [ 8 | "Swift", 9 | "Ceph", 10 | "Object Storage" 11 | ], 12 | "main": "lib/ceph-swift", 13 | "directories": { 14 | "lib": "lib" 15 | }, 16 | "dependencies": { 17 | "colors": "^1.1.2", 18 | "request": "^2.65.0", 19 | "sync-request": "^2.1.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:edge-is/ceph-swift.git" 24 | }, 25 | "licenses": [ 26 | { 27 | "type": "MIT" 28 | } 29 | ], 30 | "bugs": { 31 | "url": "https://github.com/edge-is/ceph-swift/issues" 32 | }, 33 | "engines": { 34 | "node": ">= 0.4.0" 35 | }, 36 | "devDependencies": { 37 | "jasmine-node": "^1.14.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | var colors = require('colors'); 2 | 3 | colors.setTheme({ 4 | input: 'grey', 5 | notice: 'cyan', 6 | prompt: 'grey', 7 | info: 'green', 8 | data: 'grey', 9 | help: 'cyan', 10 | warn: 'yellow', 11 | debug: 'grey', 12 | error: 'red' 13 | }); 14 | 15 | var _log = { 16 | error : function (key){ 17 | var self = this; 18 | return self._logger('error', key); 19 | }, 20 | warn : function (key){ 21 | var self = this; 22 | return self._logger('warn', key); 23 | }, 24 | notice : function (key){ 25 | var self = this; 26 | return self._logger('notice', key); 27 | }, 28 | debug : function (key){ 29 | var self = this; 30 | return self._logger('debug', key); 31 | }, 32 | info : function (key){ 33 | var self = this; 34 | return self._logger('info', key); 35 | }, 36 | _logger : function (color, functionNane){ 37 | return function (){ 38 | var string = ''; 39 | for ( var key in arguments){ 40 | var argument = arguments[key]; 41 | if(typeof argument === 'object'){ 42 | string += "\n" + JSON.stringify(argument, null, 2); 43 | }else{ 44 | string +=argument +' '; 45 | } 46 | 47 | } 48 | console.log(functionNane, colors[color](string)); 49 | } 50 | 51 | } 52 | }; 53 | 54 | module.exports = _log; 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift 2 | 3 | OpenStack Object Storage(Swift) REST client API for Node.JS 4 | 5 | Works with CEPH! 6 | 7 | ### Installing 8 | 9 | ### Code 10 | ```javascript 11 | var Swift = require('swift'); 12 | 13 | var client = new Swift({ 14 | user: 'username' 15 | , pass: 'access_key' 16 | , url: 'http://swift.server.com' 17 | , checksum : true 18 | , strictSSL : true 19 | , authenticationpath : 'path/to/authentication' /* Default to /auth/1.0 */ 20 | , checksum : true /* Send Etag and compare ETag on download? */ 21 | , strictSSL : true /* Default to true*/ 22 | , cache : { /* Cache params, default to true , take in ttl and file */ 23 | ttl : 100 24 | } 25 | }); 26 | 27 | // Authentication 28 | client.listContainers(/*optional query object*/, callback); 29 | client.retrieveAccountMetadata(callback); 30 | 31 | // Storage Services 32 | client.listObjects("containerName", { foo: 'bar' } /*query params optinal */, callback); 33 | client.createContainer("containerName", callback); 34 | client.deleteContainer("containerName", callback); 35 | client.retrieveContainerMetadata("containerName", callback); 36 | client.setContainerRead("containerName", "ACL", callback); 37 | 38 | // Object Services 39 | client.retrieveObject("containerName", "objectName", "localFile", callback); 40 | client.createObject("containerName", "objectName", callback); 41 | client.uploadObject("containerName", "localFile", "RemoteFile", callback); 42 | client.copyObject("srcContainer", "srcObject", "dstContainer", "dstObject", callback); 43 | client.deleteObject("containerName", "objectName", callback); 44 | client.retrieveObjectMetadata("containerName", "objectName", callback); 45 | client.updateObjectMetadata("containerName", "objectName", { somekey : 'somevalue' }, callback); 46 | 47 | ``` 48 | ### License 49 | 50 | MIT <3 51 | -------------------------------------------------------------------------------- /lib/ceph-swift.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * swift-ceph - CEPH Object Storage(Swift) REST client API for Node.JS 3 | * 4 | * @author agustik(https://edge.is) 5 | * @license MIT Style 6 | * Original from author firejune(to@firejune.com) 7 | */ 8 | 9 | 10 | /** 11 | * dependencies. 12 | */ 13 | 14 | var crypto = require('crypto') 15 | , request = require('request') 16 | , Syncrequest = require('sync-request') 17 | , fs = require('fs') 18 | , events = require('events') 19 | , _log = require('./log.js') 20 | , CacheFile = '' 21 | , CacheTTL = 0; 22 | 23 | var ev = new events.EventEmitter(); 24 | 25 | 26 | 27 | 28 | /** 29 | * Error codes for each level, Account -> Container -> Object 30 | * @param {string} level At what level is the call? account, container or object 31 | * @param {int} code Error code in 400-599 32 | * @param {string} action Method + URI 33 | */ 34 | var ErrorsMessages = function (level, code, action){ 35 | code = code.toString(); 36 | var message = null; 37 | var messages = { 38 | account : { 39 | '401' : 'Unauthorized' 40 | }, 41 | container : { 42 | 43 | '404' : 'Container not found', 44 | '409' : 'Conflict! Container not empty' 45 | }, 46 | object : { 47 | '400' : 'Bad request', 48 | '401' : 'Unauthorized', 49 | '404' : 'Not found', 50 | '408' : 'Timeout', 51 | '411' : 'Content length required, missing Transfer-Encoding or Content-Length request header.', 52 | '422' : 'Sent ETag does not match remote ETag.' 53 | } 54 | }; 55 | if (level in messages){ 56 | if (code in messages[level]){ 57 | message = messages[level][code]; 58 | } 59 | } 60 | if (message){ 61 | return {statusCode : code, message : message, level : level, action : action}; 62 | } 63 | 64 | return null; 65 | 66 | 67 | }; 68 | 69 | 70 | /** 71 | * Api object, holding authentication api and requests api 72 | * @type {Object} 73 | */ 74 | var api = { 75 | /** 76 | * Auhenticates with Swift server, Syncrequest 77 | * @param {object} options connection options 78 | * @return {object} object with token, account and url 79 | */ 80 | auth : function (options){ 81 | // Hack to disable SSL for sync-request 82 | if (this.strictSSL === false){ 83 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 84 | } 85 | var params = {}; 86 | var resp = Syncrequest('GET', options.url + options.authenticationpath, { 87 | headers : { 88 | 'User-Agent' : 'Ceph compatable Swift client', 89 | 'X-Auth-User': options.user, 90 | 'X-Auth-Key' : options.pass 91 | } 92 | }); 93 | if(resp.statusCode < 300 && resp.statusCode >= 200){ 94 | params['storage'] = resp.headers['x-storage-url']; 95 | params['token'] = resp.headers['x-auth-token']; 96 | return params; 97 | }else{ 98 | _log.error('Authentication')('ERROR: Could not authenticate'); 99 | return false; 100 | } 101 | }, 102 | request : function (options, callback, data){ 103 | callback = callback || function (){}; 104 | var self = this; 105 | 106 | var path = URLencodePath(options.path); 107 | 108 | var query = buildQuery(options.query); 109 | 110 | var url = self.storage + path + query 111 | var params = { 112 | method : options.method || 'GET', 113 | url : url, 114 | strictSSL : self.strictSSL, 115 | headers : { 116 | 'User-Agent' : 'Ceph compatable Swift client' 117 | , 'X-Auth-Token': self.token 118 | , 'X-Storage-Token': self.token 119 | , 'Accept' : 'application/json' 120 | } 121 | }; 122 | 123 | if (data){ 124 | params.body = data; 125 | } 126 | if ('headers' in options){ 127 | extend(params.headers, options.headers); 128 | } 129 | var req = request(params, function (){}); 130 | 131 | if (options.localFile && options.localFile !== ''){ 132 | req.pipe(fs.createWriteStream(options.localFile)) 133 | } 134 | req.on('complete', function (response, body) { 135 | 136 | var responseObject = {}; 137 | 138 | var SwiftErrorCode = ErrorsMessages(options.level, response.statusCode, params.method + ' : ' + params.url); 139 | if(SwiftErrorCode){ 140 | callback(SwiftErrorCode); 141 | }else{ 142 | responseObject.url = params.url; 143 | //responseObject.method = params.method; 144 | responseObject.headers = response.headers; 145 | responseObject.status = response.statusCode; 146 | hash = responseObject.headers.etag; 147 | //console.log(response) 148 | if(/application\/json/.test(response.headers['content-type'])){ 149 | responseObject.data = ParseIfJSON(response.body); 150 | } 151 | if(options.localFile && options.localFile !== '' && response.headers['content-type'] === 'binary/octet-stream' && self.checksum){ 152 | var file = fs.readFile(options.localFile, function (err, buffer){ 153 | var hash = HashIt(buffer); 154 | if (hash == response.headers.etag){ 155 | responseObject.localHash = hash; 156 | callback(null, responseObject) 157 | }else{ 158 | callback({ 159 | message : 'Local and remote md5 hash does not match', 160 | remote : response.headers.etag, 161 | local : hash 162 | }); 163 | } 164 | 165 | }); 166 | }else{ 167 | callback(null, responseObject); 168 | } 169 | } 170 | }); 171 | req.on('error', function (error){ 172 | _log.error('API request')(error); 173 | callback(error); 174 | }); 175 | 176 | } 177 | }; 178 | 179 | function buildQuery (obj) { 180 | var query = ''; 181 | if (!obj){ 182 | return ''; 183 | } 184 | var arr = []; 185 | for ( var key in obj){ 186 | var value = obj[key]; 187 | arr.push(key + "=" + value); 188 | } 189 | query = arr.join('&'); 190 | return '?' + query; 191 | }; 192 | 193 | function URLencodePath(path){ 194 | // urlencode path 195 | var parts; 196 | if (path.indexOf('?') > -1){ 197 | var uriargs = path.split('?'); 198 | var args = uriargs[1]; 199 | path = uriargs[0]; 200 | } 201 | 202 | if (path.indexOf('/') > -1){ 203 | parts = path.split('/'); 204 | }else{ 205 | return path; 206 | } 207 | 208 | var encoded = parts.map(function (part){ 209 | return enc(part); 210 | }); 211 | 212 | var path = encoded.join('/'); 213 | 214 | return path; 215 | } 216 | 217 | function enc(uri){ 218 | return encodeURIComponent(uri); 219 | } 220 | 221 | function ParseIfJSON(json){ 222 | try { 223 | return JSON.parse(json); 224 | }catch(e){ 225 | return json; 226 | } 227 | }; 228 | 229 | var cache = { 230 | read: function (key){ 231 | var self = this; 232 | var CacheContent = self._readCacheFile(); 233 | if (CacheContent){ 234 | if (key in CacheContent){ 235 | var now = new Date().getTime(); 236 | if(now < CacheContent[key]._EXPIRES){ 237 | return CacheContent[key]; 238 | }else{ 239 | // Delete cache 240 | self._deleteKey(key); 241 | } 242 | } 243 | } 244 | return false; 245 | }, 246 | write : function (key, obj){ 247 | var self = this; 248 | var CacheContent = self._readCacheFile(); 249 | var now = new Date().getTime(); 250 | obj._EXPIRES = now + (CacheTTL * 1000); 251 | if (CacheContent){ 252 | CacheContent[key] = obj; 253 | self._writeCacheFile(CacheContent); 254 | if ('__cache__' in CacheContent){ 255 | if ((now + 100000) > CacheContent['__cache__']){ 256 | self._clean(); 257 | } 258 | } 259 | } 260 | }, 261 | _initCache : function (){ 262 | var now = new Date().getTime(); 263 | var json = JSON.stringify({ __cache__ : now }, null, 2); 264 | fs.writeFileSync(CacheFile, json); 265 | return json; 266 | }, 267 | _parseCache : function (string){ 268 | try { 269 | return JSON.parse(string); 270 | } catch (e) { 271 | return false; 272 | } 273 | }, 274 | _deleteKey : function (key){ 275 | var self = this; 276 | 277 | var content = self._readCacheFile(); 278 | if (content){ 279 | if (key in content){ 280 | delete content[key]; 281 | self._writeCacheFile(content); 282 | return true; 283 | } 284 | } 285 | return false; 286 | }, 287 | _readCacheFile : function (){ 288 | var self = this; 289 | try { 290 | var json = fs.readFileSync(CacheFile); 291 | var obj = self._parseCache(json); 292 | return obj; 293 | } catch (e) { 294 | self._initCache(); 295 | return false; 296 | } 297 | }, 298 | _writeCacheFile : function(content){ 299 | fs.writeFileSync(CacheFile, JSON.stringify(content, null, 2)); 300 | }, 301 | _clean : function (){ 302 | var self = this; 303 | var CacheContent = self._readCacheFile(); 304 | var now = new Date().getTime(); 305 | for ( var key in CacheContent ){ 306 | var value = CacheContent[key]; 307 | if (typeof value === 'object'){ 308 | if (now > value._EXPIRES){ 309 | delete CacheContent[key]; 310 | } 311 | } 312 | } 313 | CacheContent['__cache__'] = now; 314 | self._writeCacheFile(CacheContent); 315 | 316 | } 317 | }; 318 | 319 | 320 | /** 321 | * Swif init client 322 | * @param {object} options connections options 323 | */ 324 | function Swift(options) { 325 | 326 | 327 | if(!options){ 328 | return null; 329 | } 330 | if ( ! options.user || ! options.pass || ! options.url){ 331 | return null; 332 | } 333 | 334 | if (!options.cache && options.cache !== false){ 335 | options.cache = {}; 336 | } 337 | 338 | 339 | if (options.cache!==false){ 340 | options.cache.enabled = options.cache.enabled || false; 341 | CacheTTL = options.cache.ttl || 3600; 342 | CacheFile = options.cache.file || '__swift_auth_cache.json'; 343 | } 344 | 345 | options.authenticationpath = options.authenticationpath || '/auth/1.0'; 346 | 347 | this.checksum = options.checksum || false; 348 | this.strictSSL= options.strictSSL || true; 349 | 350 | this.cached = false; 351 | this.token = ''; 352 | this.storage = ''; 353 | this.account = ''; 354 | var CacheKey = HashIt(JSON.stringify(options)); 355 | 356 | 357 | var authCache = cache.read(CacheKey); 358 | 359 | 360 | if (authCache){ 361 | var n = new Date().getTime(); 362 | this.cached = true; 363 | this.token = authCache.token; 364 | this.storage = authCache.storage; 365 | return this; 366 | } 367 | 368 | var Authenticate = api.auth(options); 369 | if(Authenticate){ 370 | this.token = Authenticate.token; 371 | this.storage = Authenticate.storage; 372 | if(options.cache !==false){ 373 | cache.write(CacheKey, this); 374 | } 375 | 376 | return this; 377 | } 378 | 379 | return null; 380 | } 381 | 382 | 383 | 384 | function extend(destination, source) { 385 | for (var property in source) 386 | destination[property] = source[property]; 387 | return destination; 388 | } 389 | 390 | /** 391 | * Creates md5 hash from file Buffer 392 | * @param {Buffer} buffer File Buffer 393 | * @return {string} md5 hash 394 | */ 395 | function HashIt(buffer){ 396 | return crypto 397 | .createHash('md5') 398 | .update(buffer) 399 | .digest('hex'); 400 | } 401 | 402 | /** 403 | * List containers available 404 | * @param {Function} callback Callback 405 | */ 406 | Swift.prototype.listContainers = function(query, callback) { 407 | if(typeof query === 'function'){ 408 | callback = query; 409 | query = {}; 410 | } 411 | api.request.call(this, { 412 | path: this.account 413 | , query : query 414 | , level : 'account' 415 | }, callback); 416 | }; 417 | 418 | /** 419 | * Get account Metadata 420 | * @param {Function} callback callback 421 | */ 422 | Swift.prototype.retrieveAccountMetadata = function(callback) { 423 | api.request.call(this, { 424 | path: this.account 425 | , level : 'account' 426 | , method: 'HEAD' 427 | }, callback); 428 | }; 429 | 430 | 431 | /** 432 | * Uploads objects to Swift 433 | * @param {String} container Container or bucket 434 | * @param {stringBuffer} local String of buffer to Uploads 435 | * @param {string} remote Name of remote variable 436 | * @param {Function} callback Callback when done 437 | */ 438 | Swift.prototype.uploadObject = function (container, local, remote, meta, callback){ 439 | if (typeof meta === 'function'){ 440 | callback = meta; 441 | } 442 | var md5="", data, value, headerKey, headers = {}; 443 | for( var key in meta){ 444 | value = meta[key]; 445 | key = key.toLowerCase(); 446 | headerKey = 'x-object-meta-' + key; 447 | headers[headerKey] = value; 448 | } 449 | 450 | // If local is Buffer then set data as buffer 451 | if (local instanceof Buffer){ 452 | data = local; 453 | }else{ 454 | // if not, then it 455 | data = fs.readFileSync(local); 456 | } 457 | if(this.checksum === true){ 458 | md5 = HashIt(data); 459 | headers['ETag'] = md5; 460 | } 461 | 462 | api.request.call(this, { 463 | path: this.account + '/' + container + '/' + remote 464 | , level : 'object' 465 | , method: 'PUT' 466 | , headers : headers 467 | }, callback, data); 468 | }; 469 | 470 | /** 471 | * List objects in container 472 | * @param {string} container container Name 473 | * @param {Function} callback callback 474 | */ 475 | Swift.prototype.listObjects = function(container, query, callback) { 476 | if (typeof query === 'function'){ 477 | callback = query; 478 | query = {}; 479 | } 480 | api.request.call(this, { 481 | path: this.account + '/' + container 482 | , query : query 483 | , level : 'container' 484 | }, callback); 485 | }; 486 | 487 | /** 488 | * Creates container 489 | * @param {string} container container Name 490 | * @param {Function} callback callback 491 | */ 492 | Swift.prototype.createContainer = function(container, callback) { 493 | api.request.call(this, { 494 | path: this.account + '/' + container 495 | , level : 'container' 496 | , method: 'PUT' 497 | }, callback); 498 | }; 499 | 500 | /** 501 | * Sets container ACL 502 | * @param {string} container container Name 503 | * @param {string} acl container ACL 504 | * @param {Function} callback callback 505 | */ 506 | 507 | Swift.prototype.setContainerRead = function (container, acl, callback){ 508 | 509 | if (Array.isArray(acl)){ 510 | acl = acl.join(', '); 511 | } 512 | var headers = { 513 | 'X-Container-Read' : acl 514 | }; 515 | 516 | api.request.call(this, { 517 | path: this.account + '/' + container 518 | , level : 'container' 519 | , method: 'POST' 520 | , headers : headers 521 | }, callback); 522 | }; 523 | 524 | /** 525 | * Deletes container 526 | * @param {string} container container Name 527 | * @param {Function} callback callback 528 | */ 529 | Swift.prototype.deleteContainer = function(container, callback) { 530 | api.request.call(this, { 531 | path: this.account + '/' + container 532 | , level : 'container' 533 | , method: 'DELETE' 534 | }, callback); 535 | }; 536 | 537 | /** 538 | * Fetch container metadata 539 | * @param {string} container container Name 540 | * @param {Function} callback callback 541 | */ 542 | Swift.prototype.retrieveContainerMetadata = function(container, callback) { 543 | api.request.call(this, { 544 | path: this.account + '/' + container 545 | , level : 'container' 546 | , method: 'HEAD' 547 | }, callback); 548 | }; 549 | 550 | /** 551 | * Fetches objet and saves to disk 552 | * @param {string} container container Name 553 | * @param {string} object object Name 554 | * @param {string} localFile [description] 555 | * @param {Function} callback callback 556 | */ 557 | Swift.prototype.retrieveObject = function(container, object, localFile, headers, callback) { 558 | if (typeof headers ==='function'){ 559 | callback = headers; 560 | } 561 | callback = callback || function (){}; 562 | headers = headers || {}; 563 | api.request.call(this, { 564 | localFile : localFile 565 | , level : 'object' 566 | , headers : headers 567 | , path: this.account + '/' + container + '/' + object 568 | }, callback); 569 | }; 570 | 571 | /** 572 | * Deletes object 573 | * @param {string} container container Name 574 | * @param {string} object object Name 575 | * @param {Function} callback callback 576 | */ 577 | Swift.prototype.deleteObject = function(container, object, callback) { 578 | api.request.call(this, { 579 | path: this.account + '/' + container + '/' + object 580 | , level : 'object' 581 | , method: 'DELETE' 582 | }, callback); 583 | }; 584 | 585 | /** 586 | * Fetches metadata for object 587 | * @param {string} container container Name 588 | * @param {string} object object Name 589 | * @param {Function} callback callback 590 | */ 591 | Swift.prototype.retrieveObjectMetadata = function(container, object, callback) { 592 | api.request.call(this, { 593 | path: this.account + '/' + container + '/' + object 594 | , level : 'object' 595 | , method: 'HEAD' 596 | }, callback); 597 | }; 598 | 599 | /** 600 | * Updates metadata for object 601 | * @param {string} container container Name 602 | * @param {string} object object Name 603 | * @param {object} meta object container metadata 604 | * @param {Function} callback callback 605 | */ 606 | Swift.prototype.updateObjectMetadata = function(container, object, meta, callback) { 607 | var value, headerKey, headers = {}; 608 | for( var key in meta){ 609 | value = meta[key]; 610 | key = key.toLowerCase(); 611 | headerKey = 'x-object-meta-' + key; 612 | headers[headerKey] = value; 613 | } 614 | 615 | api.request.call(this, { 616 | path: this.account + '/' + container + '/' + object 617 | , level : 'object' 618 | , method: 'POST' 619 | , headers : headers 620 | }, callback); 621 | }; 622 | 623 | /** 624 | * Updates metadata for container 625 | * @param {string} container container Name 626 | * @param {string} object object Name 627 | * @param {object} meta object container metadata 628 | * @param {Function} callback callback 629 | */ 630 | Swift.prototype.updateContainerMetadata = function(container, meta, callback) { 631 | var value, headerKey, headers = {}; 632 | for( var key in meta){ 633 | value = meta[key]; 634 | key = key.toLowerCase(); 635 | headerKey = 'x-container-meta-' + key; 636 | headers[headerKey] = value; 637 | } 638 | api.request.call(this, { 639 | path: this.account + '/' + container 640 | , level : 'container' 641 | , method: 'POST' 642 | , headers : headers 643 | }, callback); 644 | }; 645 | 646 | /** 647 | * Updates metadata for container 648 | * @param {string} container container Name 649 | * @param {string} object object Name 650 | * @param {object} meta object container metadata 651 | * @param {Function} callback callback 652 | */ 653 | Swift.prototype.deleteContainerMetadata = function(container, meta, callback) { 654 | var value, headerKey, headers = {}; 655 | for( var key in meta){ 656 | value = meta[key]; 657 | key = key.toLowerCase(); 658 | headerKey = 'x-remove-container-meta-' + key; 659 | headers[headerKey] = 'x'; 660 | } 661 | api.request.call(this, { 662 | path: this.account + '/' + container 663 | , level : 'container' 664 | , method: 'POST' 665 | , headers : headers 666 | }, callback); 667 | }; 668 | 669 | 670 | /** 671 | * Copy object from one container to atnother 672 | * @param {string} sourceContainer Name of container || bucket 673 | * @param {string} sourceObject Name of object 674 | * @param {string} destinationcontainer Name of container || bucket 675 | * @param {string} destinationobject Name of object 676 | * @param {Function} callback callback 677 | */ 678 | Swift.prototype.copyObject = function(sourceContainer, sourceObject, 679 | destinationcontainer, destinationobject, callback) { 680 | api.request.call(this, { 681 | path: this.account + '/' + destinationcontainer + '/' + destinationobject 682 | , level : 'object' 683 | , method: 'PUT' 684 | , headers: { 685 | 'X-Copy-From': sourceContainer + '/' + sourceObject 686 | } 687 | }, callback); 688 | }; 689 | 690 | /** 691 | * Moves object from one container to another 692 | * @param {string} sourceContainer Name of container || bucket 693 | * @param {string} sourceObject Name of object 694 | * @param {string} destinationcontainer Name of container || bucket 695 | * @param {string} destinationobject Name of object 696 | * @param {Function} callback callback 697 | */ 698 | Swift.prototype.moveObject = function(sourceContainer, sourceObject, 699 | destinationcontainer, destinationobject, callback) { 700 | callback = callback || function (){}; 701 | var self = this; 702 | // first copyObject, then delete it. 703 | self.copyObject(sourceContainer, sourceObject, destinationcontainer, destinationobject, function(err, result) { 704 | if(err){ 705 | callback(err); 706 | }else{ 707 | self.deleteObject(sourceContainer, sourceObject, callback); 708 | } 709 | }); 710 | }; 711 | 712 | module.exports = Swift; 713 | --------------------------------------------------------------------------------