├── .babelrc ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.js ├── promises.js └── readonly.js └── test ├── createReadStream.test.js ├── createWriteStream.test.js ├── mkdir.test.js ├── promises ├── createReadStream.test.js ├── createWriteStream.test.js ├── mkdir.test.js ├── readFile.test.js ├── readdir.test.js ├── rename.test.js ├── stat.test.js └── writeFile.test.js ├── readFile.test.js ├── readdir.test.js ├── readonly.test.js ├── rename.test.js ├── stat.test.js └── writeFile.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["stage-3"], 3 | "plugins": [ 4 | "add-module-exports", 5 | "transform-es2015-modules-commonjs" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | dist 40 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | .babelrc 4 | .travis.yml 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "node" 5 | after_script: 6 | - "npm run nyc-report" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Sallar Kaboli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dropbox-fs [![Build Status](https://travis-ci.org/sallar/dropbox-fs.svg?branch=master)](https://travis-ci.org/sallar/dropbox-fs) [![codecov](https://codecov.io/gh/sallar/dropbox-fs/branch/master/graph/badge.svg)](https://codecov.io/gh/sallar/dropbox-fs) [![npm version](https://badge.fury.io/js/dropbox-fs.svg)](https://www.npmjs.com/package/dropbox-fs) 2 | 3 | Node [`fs`](https://nodejs.org/api/fs.html) wrapper for Dropbox. Wraps the [Dropbox](http://npmjs.com/package/dropbox) javascript module with an async `fs`-like API so it can be used where a fileSystem API is expected. 4 | 5 | 6 | ## Installation 7 | 8 | To use this module you'll need a [Dropbox Access Token](https://blogs.dropbox.com/developers/2014/05/generate-an-access-token-for-your-own-account/). 9 | 10 | ``` bash 11 | $ npm install --save dropbox-fs 12 | ``` 13 | 14 | ## Usage 15 | 16 | ``` js 17 | const dfs = require('dropbox-fs')({ 18 | apiKey: 'DROPBOX_API_KEY_HERE' 19 | }); 20 | 21 | dfs.readdir('/Public', (err, result) => { 22 | console.log(result); // Array of files and folders 23 | }); 24 | ``` 25 | 26 | Promises are also supported. 27 | 28 | ```js 29 | const dfs = require('dropbox-fs/promises')({ 30 | apiKey: 'DROPBOX_API_KEY_HERE' 31 | }); 32 | ``` 33 | 34 | You can also pass in a `client` option if you’re using your own `dropbox` module instead of the `apiKey`. 35 | 36 | If you'd like some peace of mind then there's a read-only option too: 37 | 38 | ``` js 39 | const dfs = require('dropbox-fs/readonly')({ 40 | apiKey: 'DROPBOX_API_KEY_HERE' 41 | }); 42 | 43 | // Methods that might change data now return an error without performing any action: 44 | // - mkdir 45 | // - rename 46 | // - rmdir 47 | // - unlink 48 | // - writeFile 49 | 50 | dfs.unlink('/Public', (err) => { 51 | console.log(err); // Message saying `unlink` is not supported in read-only 52 | }); 53 | ``` 54 | 55 | ## API 56 | 57 | This module exposes the following methods: 58 | 59 | ### readdir(path[, options], callback) 60 | 61 | Reads a directory and returns a list of files and folders inside. 62 | 63 | ``` js 64 | dfs.readdir('/', (err, result) => { 65 | console.log(result); 66 | }); 67 | ``` 68 | 69 | - `path`: `String|Buffer` 70 | - `callback`: `Function` 71 | 72 | ### mkdir(path, callback) 73 | 74 | Creates a directory. 75 | 76 | ``` js 77 | dfs.mkdir('/Public/Resume', (err, stat) => { 78 | console.log(stat.name); // Resume 79 | console.log(stat.isDirectory()); // true 80 | }); 81 | ``` 82 | 83 | - `path`: `String|Buffer` 84 | - `callback`: `Function` 85 | 86 | ### rmdir(path, callback) 87 | 88 | Deletes a directory. 89 | 90 | ``` js 91 | dfs.rmdir('/Public/Resume', err => { 92 | if (!err) { 93 | console.log('Deleted.'); 94 | } 95 | }); 96 | ``` 97 | 98 | - `path`: `String|Buffer` 99 | - `callback`: `Function` 100 | 101 | ### readFile(path[, options], callback) 102 | 103 | Reads a file and returns it’s contents. 104 | 105 | ``` js 106 | // Buffer: 107 | dfs.readFile('/Work/doc.txt', (err, result) => { 108 | console.log(result.toString('utf8')); 109 | }); 110 | 111 | // String 112 | dfs.readFile('/Work/doc.txt', {encoding: 'utf8'}, (err, result) => { 113 | console.log(result); 114 | }); 115 | ``` 116 | 117 | - `path`: `String|Buffer` 118 | - `options`: Optional `String|Object` 119 | - `encoding`: `String` 120 | - `callback`: `Function` 121 | 122 | If you pass a string as the `options` parameter, it will be used as `options.encoding`. If you don’t provide an encoding, a raw `Buffer` will be passed to the callback. 123 | 124 | ### writeFile(path, data[, options], callback) 125 | 126 | Writes a file to the remote API and returns it’s `stat`. 127 | 128 | ``` js 129 | const content = fs.readFileSync('./localfile.md'); 130 | dfs.writeFile('/Public/doc.md', content, {encoding: 'utf8'}, (err, stat) => { 131 | console.log(stat.name); // doc.md 132 | }); 133 | ``` 134 | 135 | - `path`: `String|Buffer` 136 | - `data`: `String|Buffer` 137 | - `options`: Optional `String|Object` 138 | - `encoding`: `String` 139 | - `overwrite`: `Boolean` Default: `true`. 140 | - `callback`: `Function` 141 | 142 | ### rename(fromPath, toPath, callback) 143 | 144 | Renames a file (moves it to a new location). 145 | 146 | ``` js 147 | dfs.rename('/Public/doc.md', '/Backups/doc-backup.md', err => { 148 | if err { 149 | console.error('Failed!'); 150 | } else { 151 | console.log('Moved!'); 152 | } 153 | }); 154 | ``` 155 | 156 | - `path`: `String|Buffer` 157 | - `data`: `String|Buffer` 158 | - `callback`: `Function` 159 | 160 | ### stat(path, callback) 161 | 162 | Returns the file/folder information. 163 | 164 | ``` js 165 | dfs.stat('/Some/Remote/Folder/', (err, stat) => { 166 | console.log(stat.isDirectory()); // true 167 | console.log(stat.name); // Folder 168 | }); 169 | ``` 170 | 171 | - `path`: `String|Buffer` 172 | - `callback`: `Function` 173 | - `err`: `null|Error` 174 | - `stat`: [Stat](#stat-object) Object 175 | 176 | ### unlink(path, callback) 177 | 178 | Deletes a file. 179 | 180 | ``` js 181 | dfs.unlink('/Path/To/file.txt', err => { 182 | if (!err) { 183 | console.log('Deleted!'); 184 | } 185 | }); 186 | ``` 187 | 188 | - `path`: `String|Buffer` 189 | - `callback`: `Function` 190 | 191 | ## createReadStream(path) 192 | 193 | Creates a readable stream. 194 | 195 | ``` js 196 | const stream = fs.createReadStream('/test/test1.txt'); 197 | stream.on('data', data => { 198 | console.log('data', data); 199 | }); 200 | stream.on('end', () => { 201 | console.log('stream finished'); 202 | }); 203 | ``` 204 | - `path`: `String|Buffer` 205 | 206 | ## createWriteStream(path) 207 | 208 | Creates a writable stream. 209 | 210 | ``` js 211 | const stream = fs.createWriteStream('/test/test1.txt'); 212 | 213 | someStream().pipe(stream).on('finish', () => { 214 | console.log('write stream finished'); 215 | }); 216 | ``` 217 | 218 | - `path`: `String|Buffer` 219 | 220 | ## Stat Object 221 | 222 | The stat object that is returned from `writeFile()` and `stat()` contains two methods in addition to some standard information like `name`, etc: 223 | 224 | - `stat.isDirectory()` Returns `true` if the target is a directory 225 | - `stat.isFile()` Returns `true` if the target is a file 226 | 227 | ## License 228 | 229 | This software is released under the [MIT License](https://sallar.mit-license.org/). 230 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropbox-fs", 3 | "version": "1.0.0", 4 | "description": "Node FS wrapper for Dropbox API", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "babel src --out-dir dist", 8 | "format": "prettier --write '{src,test}/**/*.js'", 9 | "test": "nyc --require babel-core/register mocha --recursive", 10 | "nyc-report": "nyc report --reporter=lcov > coverage.lcov && codecov", 11 | "prepublish": "npm run build" 12 | }, 13 | "lint-staged": { 14 | "{src,test}/**/*.js": [ 15 | "prettier --write", 16 | "git add" 17 | ] 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/sallar/dropbox-fs.git" 22 | }, 23 | "keywords": [ 24 | "dropbox", 25 | "fs", 26 | "node", 27 | "filesystem", 28 | "file" 29 | ], 30 | "author": "Sallar Kaboli", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/sallar/dropbox-fs/issues" 34 | }, 35 | "homepage": "https://github.com/sallar/dropbox-fs#readme", 36 | "devDependencies": { 37 | "@types/assert": "^1.5.6", 38 | "@types/mocha": "^9.0.0", 39 | "babel-cli": "^6.16.0", 40 | "babel-core": "^6.17.0", 41 | "babel-plugin-add-module-exports": "^0.2.1", 42 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 43 | "babel-preset-stage-3": "^6.24.1", 44 | "codecov": "^1.0.1", 45 | "husky": "^0.14.3", 46 | "lint-staged": "^6.1.1", 47 | "mocha": "^3.5.3", 48 | "nyc": "^8.4.0", 49 | "prettier": "^1.10.2", 50 | "proxyquire": "^1.8.0", 51 | "pullout": "^1.0.2", 52 | "sinon": "^4.2.2", 53 | "string-to-stream": "^1.1.0", 54 | "uuid": "^2.0.3" 55 | }, 56 | "dependencies": { 57 | "dropbox": "^2.3.0", 58 | "dropbox-stream": "2.0.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Dropbox from 'dropbox'; 2 | import dropboxStream from 'dropbox-stream'; 3 | 4 | const TYPE_KEY = '@@fsType'; 5 | 6 | /** 7 | * Convert an object to fs-like stat object 8 | * 9 | * @param {Object} entry 10 | * @returns {Object} 11 | */ 12 | function __convertToStat(entry) { 13 | return { 14 | ...entry, 15 | isFile: () => entry['.tag'] === 'file', 16 | isDirectory: () => entry['.tag'] === 'folder' 17 | }; 18 | } 19 | 20 | /** 21 | * Execute a callback async 22 | * Borrowed from: https://github.com/perry-mitchell/webdav-fs/blob/master/source/index.js#L19 23 | * 24 | * @param {Function} callback 25 | * @param {Array.} args 26 | */ 27 | function __executeCallbackAsync(callback, args) { 28 | if (typeof setImmediate !== 'undefined') { 29 | setImmediate(function() { 30 | callback.apply(null, args); 31 | }); 32 | } else { 33 | setTimeout(function() { 34 | callback.apply(null, args); 35 | }, 0); 36 | } 37 | } 38 | 39 | /** 40 | * Normalize an input path string or buffer 41 | * Dropbox doesn’t allow '/' for root, it should be an empty string 42 | * and some users prefer to prefix the path with a dot. 43 | * 44 | * @param {String|Buffer} remotePath 45 | * @returns {String} 46 | */ 47 | function __normalizePath(remotePath) { 48 | if (remotePath instanceof Buffer) { 49 | remotePath = remotePath.toString('utf8'); 50 | } 51 | 52 | if (remotePath === '/') { 53 | return ''; 54 | } 55 | 56 | if (remotePath.indexOf('./') === 0) { 57 | return remotePath.replace(/\.\//, ''); 58 | } 59 | 60 | return remotePath; 61 | } 62 | 63 | /** 64 | * Create an fs-like API for Dropbox 65 | * 66 | * @param {{ 67 | * apiKey: String, 68 | * client: Dropbox 69 | * }} Configuration object 70 | * @returns {Object} 71 | */ 72 | export default ({ apiKey = null, client = null } = {}) => { 73 | if (!client && typeof apiKey === 'string') { 74 | client = new Dropbox({ 75 | accessToken: apiKey 76 | }); 77 | } else if (!client) { 78 | throw new Error('Dropbox client or apiKey should be provided.'); 79 | } 80 | 81 | const api = { 82 | // fs adapter type (for downstream integrations) 83 | [TYPE_KEY]: 'dropbox-fs', 84 | 85 | /** 86 | * Read a directory and list all the files and folders inside 87 | * 88 | * @param {String} remotePath 89 | * @param {Object} options 90 | * @param {Function} callback 91 | */ 92 | readdir(remotePath = '', options = {}, callback) { 93 | if (typeof options === 'function') { 94 | callback = options; 95 | options = {}; 96 | } 97 | 98 | const mode = options.mode || 'node'; 99 | 100 | client 101 | .filesListFolder({ path: __normalizePath(remotePath) }) 102 | .then(({ entries }) => { 103 | if (mode === 'node') { 104 | entries = entries.map(entry => entry.name); 105 | } else if (mode === 'stat') { 106 | entries = entries.map(entry => __convertToStat(entry)); 107 | } else { 108 | return callback(new Error(`Unknown mode: ${mode}`)); 109 | } 110 | __executeCallbackAsync(callback, [null, entries]); 111 | }) 112 | .catch(callback); 113 | }, 114 | 115 | /** 116 | * Create a remote directory 117 | * 118 | * @param {String} remotePath 119 | * @param {Function} callback 120 | */ 121 | mkdir(remotePath, callback) { 122 | client 123 | .filesCreateFolderV2({ path: __normalizePath(remotePath) }) 124 | .then(({ metadata }) => { 125 | metadata['.tag'] = 'folder'; 126 | metadata = __convertToStat(metadata); 127 | __executeCallbackAsync(callback, [null, metadata]); 128 | }) 129 | .catch(callback); 130 | }, 131 | 132 | /** 133 | * Read a remote file and return it’s contents 134 | * 135 | * @param {String} remotePath 136 | * @param {Object} options 137 | * @param {Function} callback 138 | */ 139 | readFile(remotePath, options = { encoding: null }, callback) { 140 | if (typeof options === 'function') { 141 | callback = options; 142 | options = { 143 | encoding: null 144 | }; 145 | } else if (typeof options === 'string') { 146 | options = { 147 | encoding: options 148 | }; 149 | } 150 | 151 | const { encoding } = options; 152 | 153 | client 154 | .filesDownload({ path: __normalizePath(remotePath) }) 155 | .then(resp => { 156 | if (resp.fileBinary) { 157 | // Probably running in node: `fileBinary` is passed 158 | let buffer = Buffer.from(resp.fileBinary, 'ascii'); 159 | buffer = encoding ? buffer.toString(encoding) : buffer; 160 | __executeCallbackAsync(callback, [null, buffer]); 161 | } else { 162 | // Probably browser environment: use FileReader + ArrayBuffer 163 | const fileReader = new FileReader(); 164 | let buffer; 165 | fileReader.onload = function() { 166 | buffer = Buffer.from(this.result); 167 | buffer = encoding 168 | ? buffer.toString(encoding) 169 | : buffer; 170 | __executeCallbackAsync(callback, [null, buffer]); 171 | }; 172 | fileReader.readAsArrayBuffer(resp.fileBlob); 173 | } 174 | }) 175 | .catch(callback); 176 | }, 177 | 178 | /** 179 | * Rename (move) a remote file 180 | * 181 | * @param {String} fromPath 182 | * @param {String} toPath 183 | * @param {Function} callback 184 | */ 185 | rename(fromPath, toPath, callback) { 186 | client 187 | .filesMoveV2({ 188 | from_path: __normalizePath(fromPath), 189 | to_path: __normalizePath(toPath) 190 | }) 191 | .then(() => { 192 | __executeCallbackAsync(callback, [null]); 193 | }) 194 | .catch(callback); 195 | }, 196 | 197 | /** 198 | * Return file or folder meta data 199 | * 200 | * @param {String} remotePath 201 | * @param {Function} callback 202 | */ 203 | stat(remotePath, callback) { 204 | client 205 | .filesGetMetadata({ path: __normalizePath(remotePath) }) 206 | .then(meta => { 207 | meta = __convertToStat(meta); 208 | __executeCallbackAsync(callback, [null, meta]); 209 | }) 210 | .catch(callback); 211 | }, 212 | 213 | /** 214 | * Delete a file or folder 215 | * 216 | * @param {String} remotePath 217 | * @param {Function} callback 218 | */ 219 | unlink(remotePath, callback) { 220 | client 221 | .filesDeleteV2({ path: __normalizePath(remotePath) }) 222 | .then(() => { 223 | __executeCallbackAsync(callback, [null]); 224 | }) 225 | .catch(callback); 226 | }, 227 | 228 | /** 229 | * create write stream 230 | * 231 | * @param {String} token 232 | * @param {String} remotePath 233 | * @returns {Stream} 234 | */ 235 | createWriteStream(filepath) { 236 | return dropboxStream.createDropboxUploadStream({ 237 | token: client.accessToken, 238 | filepath, 239 | chunkSize: 1000 * 1024 240 | }); 241 | }, 242 | 243 | /** 244 | * create read stream 245 | * 246 | * @param {String} token 247 | * @param {String} remotePath 248 | * @returns {Stream} 249 | */ 250 | createReadStream(filepath) { 251 | return dropboxStream.createDropboxDownloadStream({ 252 | token: client.accessToken, 253 | filepath 254 | }); 255 | }, 256 | 257 | /** 258 | * Write a file 259 | * 260 | * @param {String} remotePath 261 | * @param {String|Buffer} data 262 | * @param {Object|String} options 263 | * @param {Function} callback 264 | */ 265 | writeFile(remotePath, data, options = {}, callback) { 266 | if (typeof options === 'function') { 267 | callback = options; 268 | options = {}; 269 | } else if (typeof options === 'string') { 270 | options = { 271 | encoding: options 272 | }; 273 | } 274 | 275 | options = { 276 | overwrite: true, 277 | encoding: 'utf8', 278 | ...options 279 | }; 280 | 281 | const uploadOpts = { 282 | path: __normalizePath(remotePath), 283 | contents: 284 | data instanceof Buffer 285 | ? data 286 | : Buffer.from(data, options.encoding) 287 | }; 288 | 289 | if (options.overwrite !== false) { 290 | uploadOpts.mode = { 291 | '.tag': 'overwrite' 292 | }; 293 | } 294 | 295 | client 296 | .filesUpload(uploadOpts) 297 | .then(meta => { 298 | meta['.tag'] = 'file'; 299 | meta = __convertToStat(meta); 300 | __executeCallbackAsync(callback, [null, meta]); 301 | }) 302 | .catch(callback); 303 | } 304 | }; 305 | 306 | api.rmdir = api.unlink; 307 | 308 | return api; 309 | }; 310 | -------------------------------------------------------------------------------- /src/promises.js: -------------------------------------------------------------------------------- 1 | import Dropbox from 'dropbox'; 2 | import dropboxStream from 'dropbox-stream'; 3 | 4 | const TYPE_KEY = '@@fsType'; 5 | /** 6 | * Create an fs-like API for Dropbox 7 | * 8 | * @param {{ 9 | * apiKey: String, 10 | * client: Dropbox 11 | * }} Configuration object 12 | * @returns {Object} 13 | */ 14 | export default ({ apiKey = null, client = null } = {}) => { 15 | if (!client && typeof apiKey === 'string') { 16 | client = new Dropbox({ 17 | accessToken: apiKey 18 | }); 19 | } else if (!client) { 20 | throw new Error('Dropbox client or apiKey should be provided.'); 21 | } 22 | 23 | const fs = require('./index')({ apiKey, client }); 24 | 25 | const api = { 26 | [TYPE_KEY]: 'dropbox-fs', 27 | 28 | /** 29 | * Read a directory and list all the files and folders inside 30 | * 31 | * @param {String} remotePath 32 | * @param {Object} options 33 | * @return {Promise<(DropboxTypes.files.FileMetadataReference | DropboxTypes.files.FolderMetadataReference | DropboxTypes.files.DeletedMetadataReference)[]>} 34 | */ 35 | readdir(remotePath = '', options = {}) { 36 | return new Promise((resolve, reject) => { 37 | fs.readdir(remotePath, options, (err, entries) => { 38 | if (err) { 39 | return reject(err); 40 | } 41 | resolve(entries); 42 | }); 43 | }); 44 | }, 45 | 46 | /** 47 | * Create a remote directory 48 | * 49 | * @param {String} remotePath 50 | * @return {Promise} 51 | */ 52 | mkdir(remotePath) { 53 | return new Promise((resolve, reject) => { 54 | fs.mkdir(remotePath, (err, metadata) => { 55 | if (err) { 56 | return reject(err); 57 | } 58 | resolve(metadata); 59 | }); 60 | }); 61 | }, 62 | 63 | /** 64 | * Read a remote file and return it’s contents 65 | * 66 | * @param {String} remotePath 67 | * @param {Object} options 68 | * @return {Promise} 69 | */ 70 | async readFile(remotePath, options = { encoding: null }) { 71 | return new Promise((resolve, reject) => { 72 | fs.readFile(remotePath, options, (err, buffer) => { 73 | if (err) { 74 | return reject(err); 75 | } 76 | resolve(buffer); 77 | }); 78 | }); 79 | }, 80 | 81 | /** 82 | * Rename (move) a remote file 83 | * 84 | * @param {String} fromPath 85 | * @param {String} toPath 86 | * @return {Promise} 87 | */ 88 | rename(fromPath, toPath) { 89 | return new Promise((resolve, reject) => { 90 | fs.rename(fromPath, toPath, err => { 91 | if (err) { 92 | return reject(err); 93 | } 94 | resolve(); 95 | }); 96 | }); 97 | }, 98 | 99 | /** 100 | * Return file or folder meta data 101 | * 102 | * @param {String} remotePath 103 | * @return {Promise} 104 | */ 105 | stat(remotePath) { 106 | return new Promise((resolve, reject) => { 107 | fs.stat(remotePath, (err, meta) => { 108 | if (err) { 109 | return reject(err); 110 | } 111 | resolve(meta); 112 | }); 113 | }); 114 | }, 115 | 116 | /** 117 | * Delete a file or folder 118 | * 119 | * @param {String} remotePath 120 | * @return {Promise} 121 | */ 122 | unlink(remotePath) { 123 | return new Promise((resolve, reject) => { 124 | fs.unlink(remotePath, err => { 125 | if (err) { 126 | return reject(err); 127 | } 128 | resolve(); 129 | }); 130 | }); 131 | }, 132 | 133 | /** 134 | * create write stream 135 | * 136 | * @param {String} token 137 | * @param {String} remotePath 138 | * @returns {Stream} 139 | */ 140 | createWriteStream(filepath) { 141 | return dropboxStream.createDropboxUploadStream({ 142 | token: client.accessToken, 143 | filepath, 144 | chunkSize: 1000 * 1024 145 | }); 146 | }, 147 | 148 | /** 149 | * create read stream 150 | * 151 | * @param {String} token 152 | * @param {String} remotePath 153 | * @returns {Stream} 154 | */ 155 | createReadStream(filepath) { 156 | return dropboxStream.createDropboxDownloadStream({ 157 | token: client.accessToken, 158 | filepath 159 | }); 160 | }, 161 | 162 | /** 163 | * Write a file 164 | * 165 | * @param {String} remotePath 166 | * @param {String|Buffer} data 167 | * @param {Object|String} options 168 | * @return {Promise} 169 | */ 170 | writeFile(remotePath, data, options = {}) { 171 | return new Promise((resolve, reject) => { 172 | fs.writeFile(remotePath, data, options, (err, meta) => { 173 | if (err) { 174 | return reject(err); 175 | } 176 | resolve(meta); 177 | }); 178 | }); 179 | } 180 | }; 181 | 182 | api.rmdir = api.unlink; 183 | 184 | return api; 185 | }; 186 | -------------------------------------------------------------------------------- /src/readonly.js: -------------------------------------------------------------------------------- 1 | import normalDropboxFS from './index'; 2 | 3 | // List of API methods that should be prevented in read-only mode 4 | const dangerousMethods = [ 5 | 'mkdir', 6 | 'rename', 7 | 'rmdir', 8 | 'unlink', 9 | 'writeFile', 10 | 'createWriteStream' 11 | ]; 12 | 13 | /** 14 | * Create a read-only fs-like API for Dropbox 15 | * 16 | * @param {{ 17 | * apiKey: String, 18 | * client: Dropbox, 19 | * }} Configuration object 20 | * @returns {Object} 21 | */ 22 | export default ({ client, apiKey }) => { 23 | const api = normalDropboxFS({ client, apiKey }); 24 | 25 | const returnError = method => (...methodArgs) => { 26 | const cb = methodArgs[methodArgs.length - 1]; 27 | cb(`${method} is not supported in read-only mode`); 28 | }; 29 | 30 | // Replace dangerous methods with safe ones 31 | dangerousMethods.forEach(method => { 32 | api[method] = returnError(method); 33 | }); 34 | 35 | return api; 36 | }; 37 | -------------------------------------------------------------------------------- /test/createReadStream.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import pullout from 'pullout'; 3 | 4 | const fs = require('../src/index')({ 5 | apiKey: process.env.DROPBOX_API_KEY 6 | }); 7 | 8 | describe('fs.createReadStream()', function() { 9 | this.timeout(10000); 10 | 11 | it('Reads a file correctly', done => { 12 | const stream = fs.createReadStream('/list-test/test1.txt'); 13 | pullout(stream, (err, result) => { 14 | assert.equal(err, null); 15 | assert.ok(result instanceof Buffer); 16 | assert.equal(result.toString('utf8'), 'one\n'); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/createWriteStream.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { v4 } from 'uuid'; 3 | import pullout from 'pullout'; 4 | import stringToStream from 'string-to-stream'; 5 | 6 | const fs = require('../src/index')({ 7 | apiKey: process.env.DROPBOX_API_KEY 8 | }); 9 | 10 | describe('fs.createWriteStream()', function() { 11 | this.timeout(10000); 12 | 13 | const testFileName = `${v4()}.txt`; 14 | 15 | it('Writes a file correctly', done => { 16 | const name = `/write-test/${testFileName}`; 17 | const stream = fs.createWriteStream(name); 18 | 19 | stringToStream('testdata') 20 | .pipe(stream) 21 | .on('metadata', () => { 22 | fs.stat(name, (err, stat) => { 23 | assert.equal(err, null); 24 | assert.equal(stat.isFile(), true); 25 | assert.equal(stat.name, testFileName); 26 | done(); 27 | }); 28 | }); 29 | }); 30 | 31 | it('Deletes a file correctly', done => { 32 | fs.unlink(`/write-test/${testFileName}`, (err, stat) => { 33 | assert.equal(err, null); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/mkdir.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { v4 } from 'uuid'; 3 | 4 | const fs = require('../src/index')({ 5 | apiKey: process.env.DROPBOX_API_KEY 6 | }); 7 | 8 | describe('fs.mkdir()', function() { 9 | this.timeout(10000); 10 | 11 | const dirName = v4(); 12 | 13 | it('Creates a folder', done => { 14 | fs.mkdir(`/mkdir-test/${dirName}`, (err, stat) => { 15 | assert.equal(err, null); 16 | assert.equal(stat.isDirectory(), true); 17 | assert.equal(stat.name, dirName); 18 | done(); 19 | }); 20 | }); 21 | 22 | it('Deletes a folder', done => { 23 | fs.rmdir(`/mkdir-test/${dirName}`, err => { 24 | assert.equal(err, null); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/promises/createReadStream.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import pullout from 'pullout'; 3 | 4 | const fs = require('../../src/promises')({ 5 | apiKey: process.env.DROPBOX_API_KEY 6 | }); 7 | 8 | describe('fs.createReadStream() promise', function() { 9 | this.timeout(10000); 10 | 11 | it('Reads a file correctly', done => { 12 | const stream = fs.createReadStream('/list-test/test1.txt'); 13 | pullout(stream, (err, result) => { 14 | assert.equal(err, null); 15 | assert.ok(result instanceof Buffer); 16 | assert.equal(result.toString('utf8'), 'one\n'); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/promises/createWriteStream.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { v4 } from 'uuid'; 3 | import stringToStream from 'string-to-stream'; 4 | 5 | const fs = require('../../src/promises')({ 6 | apiKey: process.env.DROPBOX_API_KEY 7 | }); 8 | 9 | describe('fs.createWriteStream() promise', function() { 10 | this.timeout(10000); 11 | 12 | const testFileName = `${v4()}.txt`; 13 | 14 | it('Writes a file correctly', done => { 15 | const name = `/write-test/${testFileName}`; 16 | const stream = fs.createWriteStream(name); 17 | 18 | stringToStream('testdata') 19 | .pipe(stream) 20 | .on('metadata', async () => { 21 | try { 22 | const stat = await fs.stat(name); 23 | assert.equal(stat.isFile(), true); 24 | assert.equal(stat.name, testFileName); 25 | done(); 26 | } catch (error) { 27 | done(error); 28 | } 29 | }); 30 | }); 31 | 32 | it('Deletes a file correctly', () => { 33 | return fs.unlink(`/write-test/${testFileName}`); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/promises/mkdir.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { v4 } from 'uuid'; 3 | 4 | const fs = require('../../src/promises')({ 5 | apiKey: process.env.DROPBOX_API_KEY 6 | }); 7 | 8 | describe('fs.mkdir() promise', function() { 9 | this.timeout(10000); 10 | 11 | const dirName = v4(); 12 | 13 | it('Creates a folder', async () => { 14 | const stat = await fs.mkdir(`/mkdir-test/${dirName}`); 15 | assert.equal(stat.isDirectory(), true); 16 | assert.equal(stat.name, dirName); 17 | }); 18 | 19 | it('Deletes a folder', () => { 20 | return fs.rmdir(`/mkdir-test/${dirName}`); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/promises/readFile.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../../src/promises')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.readFile() promise', function() { 8 | this.timeout(10000); 9 | 10 | it('Reads a file correctly', async () => { 11 | const result = await fs.readFile('/list-test/test1.txt'); 12 | assert.ok(result instanceof Buffer); 13 | assert.ok(result.toString('utf8') === 'one\n'); 14 | }); 15 | 16 | it('Reads a file correctly with options', async () => { 17 | const result = await fs.readFile('/list-test/test2.txt', { 18 | encoding: 'base64' 19 | }); 20 | assert.ok(typeof result === 'string'); 21 | assert.ok(result === 'dHdvCg=='); 22 | }); 23 | 24 | it('Reads a file correctly with utf-8', async () => { 25 | const result = await fs.readFile('/utf8-test/utf8.txt', { 26 | encoding: 'utf8' 27 | }); 28 | 29 | assert.ok(typeof result === 'string'); 30 | assert.ok(result === 'brök'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/promises/readdir.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../../src/promises')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.readdir() promise', function() { 8 | this.timeout(10000); 9 | 10 | it('Reads root folder', async () => { 11 | const result = await fs.readdir('/'); 12 | assert.ok(Array.isArray(result)); 13 | assert.ok(result.indexOf('list-test') > -1); 14 | }); 15 | 16 | it('Handles buffer as filePath and cleans URL', () => { 17 | const path = Buffer.from('./', 'utf8'); 18 | return fs.readdir(path); 19 | }); 20 | 21 | it('Throws error for unknown mode', async () => { 22 | try { 23 | await fs.readdir('/', { mode: 'unknown' }); 24 | } catch (error) { 25 | assert.ok(error instanceof Error); 26 | } 27 | }); 28 | 29 | it('Reads sub folders', async () => { 30 | const result = await fs.readdir('/list-test'); 31 | assert.ok(Array.isArray(result)); 32 | assert.ok(result.length === 3); 33 | assert.ok(result.indexOf('test1.txt') > -1); 34 | assert.ok(result.indexOf('test2.txt') > -1); 35 | assert.ok(result.indexOf('test3.txt') > -1); 36 | }); 37 | 38 | it('Returns stat if option provided', async () => { 39 | const result = await fs.readdir('/list-test', { 40 | mode: 'stat' 41 | }); 42 | result.forEach(file => { 43 | assert.ok(typeof file.isDirectory === 'function'); 44 | assert.ok(typeof file.isFile === 'function'); 45 | assert.ok(typeof file.name === 'string'); 46 | assert.ok(file.isFile()); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/promises/rename.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../../src/promises')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.rename() promise', function() { 8 | this.timeout(10000); 9 | 10 | it('Renames correctly', async () => { 11 | await fs.rename('/rename-test/before.txt', '/rename-test/after.txt'); 12 | 13 | const content = await fs.readFile('/rename-test/after.txt', 'utf8'); 14 | 15 | assert.ok(content === 'one\n'); 16 | 17 | await fs.rename('/rename-test/after.txt', '/rename-test/before.txt'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/promises/stat.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../../src/promises')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.stat() promise', function() { 8 | this.timeout(10000); 9 | 10 | it('Returns correct stat for a file', async () => { 11 | const stat = await fs.stat('/list-test/test1.txt'); 12 | 13 | assert.ok(typeof stat.isDirectory === 'function'); 14 | assert.ok(!stat.isDirectory()); 15 | assert.ok(stat.isFile()); 16 | assert.ok(stat.name === 'test1.txt'); 17 | }); 18 | 19 | it('Returns correct stat for a directory', async () => { 20 | const stat = await fs.stat('/list-test'); 21 | 22 | assert.ok(typeof stat.isDirectory === 'function'); 23 | assert.ok(stat.isDirectory()); 24 | assert.ok(!stat.isFile()); 25 | assert.ok(stat.name === 'list-test'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/promises/writeFile.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { v4 } from 'uuid'; 3 | 4 | const fs = require('../../src/promises')({ 5 | apiKey: process.env.DROPBOX_API_KEY 6 | }); 7 | 8 | describe('fs.writeFile() promise', function() { 9 | this.timeout(10000); 10 | 11 | const testFileName = `${v4()}.txt`; 12 | 13 | it('Writes a file correctly', async () => { 14 | const stat = await fs.writeFile( 15 | `/write-test/${testFileName}`, 16 | 'testdata' 17 | ); 18 | 19 | assert.ok(stat.isFile()); 20 | assert.ok(stat.name === testFileName); 21 | }); 22 | 23 | it('Overwrite throws error when disabled', async () => { 24 | try { 25 | await fs.writeFile(`/write-test/${testFileName}`, 'testdata2', { 26 | overwrite: false 27 | }); 28 | } catch (_) { 29 | assert.ok(true); 30 | } 31 | }); 32 | 33 | it('Overwrites and sets encoding', async () => { 34 | const options = { 35 | encoding: 'base64', 36 | overwrite: true 37 | }; 38 | 39 | const stat = await fs.writeFile( 40 | `/write-test/${testFileName}`, 41 | 'dHdvCg==', 42 | options 43 | ); 44 | 45 | assert.ok(stat.isFile()); 46 | assert.ok(stat.name === testFileName); 47 | const content = await fs.readFile( 48 | `/write-test/${testFileName}`, 49 | 'utf8' 50 | ); 51 | assert.ok(content === 'two\n'); 52 | }); 53 | 54 | it('Deletes a file correctly', () => { 55 | return fs.unlink(`/write-test/${testFileName}`); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/readFile.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../src/index')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.readFile()', function() { 8 | this.timeout(10000); 9 | 10 | it('Reads a file correctly', done => { 11 | fs.readFile('/list-test/test1.txt', (err, result) => { 12 | assert.equal(err, null); 13 | assert.ok(result instanceof Buffer); 14 | assert.equal(result.toString('utf8'), 'one\n'); 15 | done(); 16 | }); 17 | }); 18 | 19 | it('Reads a file correctly with options', done => { 20 | fs.readFile( 21 | '/list-test/test2.txt', 22 | { 23 | encoding: 'base64' 24 | }, 25 | (err, result) => { 26 | assert.equal(err, null); 27 | assert.equal(typeof result, 'string'); 28 | assert.equal(result, 'dHdvCg=='); 29 | done(); 30 | } 31 | ); 32 | }); 33 | 34 | it('Reads a file correctly with utf-8', done => { 35 | fs.readFile( 36 | '/utf8-test/utf8.txt', 37 | { 38 | encoding: 'utf8' 39 | }, 40 | (err, result) => { 41 | assert.equal(err, null); 42 | assert.equal(typeof result, 'string'); 43 | assert.equal(result, 'brök'); 44 | done(); 45 | } 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/readdir.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../src/index')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.readdir()', function() { 8 | this.timeout(10000); 9 | 10 | it('Reads root folder', done => { 11 | fs.readdir('/', (err, result) => { 12 | assert.equal(err, null); 13 | assert.ok(Array.isArray(result)); 14 | assert.ok(result.indexOf('list-test') > -1); 15 | done(); 16 | }); 17 | }); 18 | 19 | it('Handles buffer as filePath and cleans URL', done => { 20 | // This is buffer to test internal methods 21 | const path = Buffer.from('./', 'utf8'); 22 | fs.readdir(path, (err, result) => { 23 | assert.equal(err, null); 24 | done(); 25 | }); 26 | }); 27 | 28 | it('Throws error for unknown mode', done => { 29 | fs.readdir('/', { mode: 'unknown' }, (err, result) => { 30 | assert.ok(err instanceof Error); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('Reads sub folders', done => { 36 | fs.readdir('/list-test', (err, result) => { 37 | assert.equal(err, null); 38 | assert.ok(Array.isArray(result)); 39 | assert.equal(result.length, 3); 40 | assert.ok(result.indexOf('test1.txt') > -1); 41 | assert.ok(result.indexOf('test2.txt') > -1); 42 | assert.ok(result.indexOf('test3.txt') > -1); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('Returns stat if option provided', done => { 48 | fs.readdir( 49 | '/list-test', 50 | { 51 | mode: 'stat' 52 | }, 53 | (err, result) => { 54 | assert.equal(err, null); 55 | result.forEach(file => { 56 | assert.equal(typeof file.isDirectory, 'function'); 57 | assert.equal(typeof file.isFile, 'function'); 58 | assert.equal(typeof file.name, 'string'); 59 | assert.equal(file.isFile(), true); 60 | }); 61 | done(); 62 | } 63 | ); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/readonly.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import proxyquire from 'proxyquire'; 3 | import { spy, stub } from 'sinon'; 4 | 5 | // Use proxyquire to inject our own stub FS factory so we don't have to hit Dropbox 6 | const fsFactory = stub(); 7 | const readonly = proxyquire('../src/readonly', { 8 | './index': fsFactory 9 | }); 10 | 11 | describe('readonly', function() { 12 | describe('#ctor', function() { 13 | beforeEach(() => fsFactory.returns({})); 14 | afterEach(() => fsFactory.reset()); 15 | 16 | it('passes on `apiKey`', function() { 17 | const apiKey = 'dummy'; 18 | readonly({ apiKey }); 19 | assert(fsFactory.alwaysCalledWithMatch({ apiKey })); 20 | }); 21 | 22 | it('passes on `client`', function() { 23 | const client = {}; 24 | readonly({ client }); 25 | assert(fsFactory.alwaysCalledWithMatch({ client })); 26 | }); 27 | }); 28 | 29 | describeSafeMethod('readdir', 'dummypath'); 30 | describeSafeMethod('readFile', 'dummypath'); 31 | describeSafeMethod('stat', 'dummypath'); 32 | describeSafeMethod('createReadStream', 'dummypath'); 33 | 34 | function describeSafeMethod(name, ...args) { 35 | describe(`#${name}`, () => { 36 | // Stub out the corresponding method on the normal API 37 | const fsMethod = spy(); 38 | beforeEach(() => fsMethod.resetHistory()); 39 | 40 | beforeEach(() => fsFactory.returns({ [name]: fsMethod })); 41 | afterEach(() => fsFactory.reset()); 42 | 43 | it('makes call to fs', () => { 44 | const readonlyFs = readonly({ client: {} }); 45 | 46 | readonlyFs[name](...args, noopCallback); 47 | 48 | assert(fsMethod.alwaysCalledWithExactly(...args, noopCallback)); 49 | }); 50 | }); 51 | } 52 | 53 | describeDangerousMethod('mkdir', 'dummypath'); 54 | describeDangerousMethod('rename', 'dummypath', 'dummypath'); 55 | describeDangerousMethod('rmdir', 'dummypath'); 56 | describeDangerousMethod('unlink', 'dummypath'); 57 | describeDangerousMethod('writeFile', 'dummypath', null); 58 | describeDangerousMethod('createWriteStream', 'dummypath'); 59 | 60 | function describeDangerousMethod(name, ...args) { 61 | describe(`#${name}`, () => { 62 | // Stub out the corresponding method on the normal API 63 | const fsMethod = spy(); 64 | beforeEach(() => fsMethod.resetHistory()); 65 | 66 | beforeEach(() => fsFactory.returns({ [name]: fsMethod })); 67 | afterEach(() => fsFactory.reset()); 68 | 69 | it('does not call fs', () => { 70 | const readonlyFs = readonly({ client: {} }); 71 | 72 | readonlyFs[name](...args, noopCallback); 73 | 74 | assert.equal(fsMethod.called, false); 75 | }); 76 | 77 | it('returns an error', () => { 78 | const readonlyFs = readonly({ client: {} }); 79 | 80 | const callback = spy(); 81 | readonlyFs[name](...args, callback); 82 | 83 | assert( 84 | callback.alwaysCalledWithExactly( 85 | `${name} is not supported in read-only mode` 86 | ) 87 | ); 88 | }); 89 | }); 90 | } 91 | }); 92 | 93 | const noopCallback = () => {}; 94 | -------------------------------------------------------------------------------- /test/rename.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../src/index')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.rename()', function() { 8 | this.timeout(10000); 9 | 10 | it('Renames correctly', done => { 11 | fs.rename( 12 | '/rename-test/before.txt', 13 | '/rename-test/after.txt', 14 | moveErr => { 15 | assert.equal(moveErr, null); 16 | fs.readFile( 17 | '/rename-test/after.txt', 18 | 'utf8', 19 | (err, content) => { 20 | assert.equal(content, 'one\n'); 21 | fs.rename( 22 | '/rename-test/after.txt', 23 | '/rename-test/before.txt', 24 | moveBackErr => { 25 | assert.equal(moveBackErr, null); 26 | done(); 27 | } 28 | ); 29 | } 30 | ); 31 | } 32 | ); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/stat.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const fs = require('../src/index')({ 4 | apiKey: process.env.DROPBOX_API_KEY 5 | }); 6 | 7 | describe('fs.stat()', function() { 8 | this.timeout(10000); 9 | 10 | it('Returns correct stat for a file', done => { 11 | fs.stat('/list-test/test1.txt', (err, stat) => { 12 | assert.equal(err, null); 13 | assert.equal(typeof stat.isDirectory, 'function'); 14 | assert.equal(stat.isDirectory(), false); 15 | assert.equal(stat.isFile(), true); 16 | assert.equal(stat.name, 'test1.txt'); 17 | done(); 18 | }); 19 | }); 20 | 21 | it('Returns correct stat for a directory', done => { 22 | fs.stat('/list-test', (err, stat) => { 23 | assert.equal(err, null); 24 | assert.equal(typeof stat.isDirectory, 'function'); 25 | assert.equal(stat.isDirectory(), true); 26 | assert.equal(stat.isFile(), false); 27 | assert.equal(stat.name, 'list-test'); 28 | done(); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/writeFile.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { v4 } from 'uuid'; 3 | 4 | const fs = require('../src/index')({ 5 | apiKey: process.env.DROPBOX_API_KEY 6 | }); 7 | 8 | describe('fs.writeFile()', function() { 9 | this.timeout(10000); 10 | 11 | const testFileName = `${v4()}.txt`; 12 | 13 | it('Writes a file correctly', done => { 14 | fs.writeFile(`/write-test/${testFileName}`, 'testdata', (err, stat) => { 15 | assert.equal(err, null); 16 | assert.equal(stat.isFile(), true); 17 | assert.equal(stat.name, testFileName); 18 | done(); 19 | }); 20 | }); 21 | 22 | it('Overwrite throws error when disabled', done => { 23 | fs.writeFile( 24 | `/write-test/${testFileName}`, 25 | 'testdata2', 26 | { 27 | overwrite: false 28 | }, 29 | (err, stat) => { 30 | assert.equal(typeof err, 'object'); 31 | done(); 32 | } 33 | ); 34 | }); 35 | 36 | it('Overwrites and sets encoding', done => { 37 | fs.writeFile( 38 | `/write-test/${testFileName}`, 39 | 'dHdvCg==', 40 | { 41 | encoding: 'base64', 42 | overwrite: true 43 | }, 44 | (err, stat) => { 45 | assert.equal(err, null); 46 | assert.equal(stat.isFile(), true); 47 | assert.equal(stat.name, testFileName); 48 | fs.readFile( 49 | `/write-test/${testFileName}`, 50 | 'utf8', 51 | (err, content) => { 52 | assert.equal(err, null); 53 | assert.equal(content, 'two\n'); 54 | done(); 55 | } 56 | ); 57 | } 58 | ); 59 | }); 60 | 61 | it('Deletes a file correctly', done => { 62 | fs.unlink(`/write-test/${testFileName}`, (err, stat) => { 63 | assert.equal(err, null); 64 | done(); 65 | }); 66 | }); 67 | }); 68 | --------------------------------------------------------------------------------