UNPKG

4.66 kBJavaScriptView Raw
1'use strict';
2
3const Promise = require('bluebird');
4const Mime = require('mime');
5const Fs = Promise.promisifyAll(require('fs'), {suffix: 'Promised'});
6const Path = require('path');
7
8const Tools = require('unifile-common-tools');
9
10const NAME = 'fs';
11
12const validatePath = Symbol('validatePath');
13
14function statToFileInfos(filename, stat) {
15 const isDir = stat.isDirectory();
16 return {
17 size: stat.size,
18 modified: stat.mtime,
19 name: filename,
20 isDir: isDir,
21 mime: isDir ? 'application/directory' : Mime.getType(filename)
22 };
23}
24
25/**
26 * Service connector for the local filesystem.
27 */
28class FsConnector {
29 /**
30 * @constructor
31 * @param {Object} config - Configuration object
32 * @param {string|Array<string>} [config.sandbox] - Restrict connector access to this path (if string)
33 * or these paths (if array)
34 * @param {string} [config.rootPath] - Path against all relative paths will be resolved.
35 * Default to the first sandbox path if given or /.
36 * @param {boolean} [config.showHiddenFiles=false] - Flag to show hidden files.
37 * @param {ConnectorStaticInfos} [config.infos] - Connector infos to override
38 * @throws {Error} Invalid sandbox path.
39 */
40 constructor(config) {
41 const conf = config || {};
42 this.showHiddenFile = conf.showHiddenFile;
43 this.infos = Tools.mergeInfos(conf.infos, {
44 name: NAME,
45 displayName: 'Local',
46 icon: '',
47 description: 'Edit files on your local drive.'
48 });
49 this.name = this.infos.name;
50
51 if(!conf.sandbox) this.sandbox = [];
52 else if(conf.sandbox.constructor === String) this.sandbox = [conf.sandbox];
53 else if(Array.isArray(conf.sandbox)) this.sandbox = conf.sandbox;
54 else throw new Error('Invalid sandbox path. Must be a string or an array');
55
56 if(conf.rootPath) this.rootPath = conf.rootPath;
57 else if(this.sandbox.length > 0) this.rootPath = this.sandbox[0];
58 else this.rootPath = '/';
59 }
60
61 getInfos(session) {
62 return Object.assign({
63 isLoggedIn: true,
64 isOAuth: false,
65 username: process.env.USER
66 }, this.infos);
67 }
68
69 // Auth methods are useless here
70
71 getAuthorizeURL(session) {
72 return Promise.resolve('');
73 }
74
75 setAccessToken(session, token) {
76 return Promise.resolve(token);
77 }
78
79 clearAccessToken(session) {
80 return Promise.resolve();
81 }
82
83 login(session, loginInfos) {
84 return new Promise.resolve();
85 }
86
87 //Filesystem commands
88
89 readdir(session, path) {
90 try {
91 const securePath = this[validatePath](path);
92 return Fs.readdirPromised(securePath)
93 .reduce((memo, entry) => {
94 if(this.showHiddenFile || entry.charAt(0) != '.') {
95 return Fs.statPromised(Path.resolve(securePath, entry))
96 .then((stat) => {
97 memo.push(statToFileInfos(entry, stat));
98 return memo;
99 });
100 } else {
101 return memo;
102 }
103 }, []);
104 } catch (e) {
105 return Promise.reject(e);
106 }
107 }
108
109 stat(session, path) {
110 try {
111 const securePath = this[validatePath](path);
112 return Fs.statPromised(securePath)
113 .then((stat) => {
114 return statToFileInfos(Path.basename(securePath), stat);
115 });
116 } catch (e) {
117 return Promise.reject(e);
118 }
119 }
120
121 mkdir(session, path) {
122 try {
123 return Fs.mkdirPromised(this[validatePath](path));
124 } catch (e) {
125 return Promise.reject(e);
126 }
127 }
128
129 writeFile(session, path, data) {
130 try {
131 return Fs.writeFilePromised(this[validatePath](path), data);
132 } catch (e) {
133 return Promise.reject(e);
134 }
135 }
136
137 createWriteStream(session, path) {
138 return Fs.createWriteStream(this[validatePath](path));
139 }
140
141 readFile(session, path) {
142 try {
143 return Fs.readFilePromised(this[validatePath](path));
144 } catch (e) {
145 return Promise.reject(e);
146 }
147 }
148
149 createReadStream(session, path) {
150 return Fs.createReadStream(this[validatePath](path), {encoding: 'utf8'});
151 }
152
153 rename(session, src, dest) {
154 try {
155 return Fs.renamePromised(this[validatePath](src), this[validatePath](dest));
156 } catch (e) {
157 return Promise.reject(e);
158 }
159 }
160
161 unlink(session, path) {
162 try {
163 return Fs.unlinkPromised(this[validatePath](path));
164 } catch (e) {
165 return Promise.reject(e);
166 }
167 }
168
169 rmdir(session, path) {
170 try {
171 return Fs.rmdirPromised(this[validatePath](path));
172 } catch (e) {
173 return Promise.reject(e);
174 }
175 }
176
177 batch(session, actions, message) {
178 return Tools.simpleBatch(this, session, actions);
179 }
180
181 /**
182 * Ensure the given path is in the user-defined sandbox
183 * @private
184 */
185 [validatePath](path) {
186 const absolutePath = Path.resolve(this.rootPath, path);
187 if(this.sandbox.length !== 0 && !this.sandbox.some((sandboxPath) => absolutePath.startsWith(sandboxPath)))
188 throw new Error(`Path is out of the sandbox: ${absolutePath}`);
189 return absolutePath;
190 }
191}
192
193module.exports = FsConnector;