fcs
2025-04-10 0163870d662dc5b7d69ceff977490ede594f7d82
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/**
 * Created by yang on 2015/8/25.
 */
'use strict';
 
var path = require('path');
var fs = require('fs');
var net = require('net');
var _ = require('lodash');
var is = require('is-type-of');
var Promise = require('bluebird');
var Tracker = require('./tracker.js');
var logger = require('./logger.js');
var helpers = require('./helpers.js');
var protocol = require('./protocol.js');
 
 
// exports
module.exports = FdfsClient;
 
var defaults = {
    charset: 'utf8',
    trackers: [],
    // 默认超时时间10s
    timeout: 10000,
    // 默认后缀
    // 当获取不到文件后缀时使用
    defaultExt: ''
};
 
function FdfsClient(config) {
    // config global logger
    if (config && config.logger) {
        logger.setLogger(config.logger);
    }
    this.config = _.extend({}, defaults, config);
 
    this._checkConfig();
}
 
// private methods
/**
 * 确认配置是否合法
 * @private
 */
FdfsClient.prototype._checkConfig = function() {
 
    // 验证trackers是否合法
    if (!this.config.trackers) {
        throw new Error('you must specify "trackers" in config.');
    }
 
    if (!Array.isArray(this.config.trackers)) {
        this.config.trackers = [this.config.trackers];
    }
 
    if (this.config.trackers.length === 0) {
        throw new Error('"trackers" in config is empty.');
    }
 
    this.config.trackers.forEach(function(tracker) {
        if (!tracker.host || !tracker.port) {
            throw new Error('"trackers" in config is invalid, every tracker must all have "host" and "port".');
        }
    });
};
 
/**
 * 按顺序获取可用的tracker
 */
FdfsClient.prototype._getTracker = function() {
    if (!this._trackerIndex) {
        this._trackerIndex = 0;
    } else {
        this._trackerIndex++;
        if (this._trackerIndex >= this.config.trackers.length) {
            this._trackerIndex = 0;
        }
    }
    var _self = this;
    var _tracker = undefined;
    var config = this._getTrackerConfig(this._trackerIndex);
    return _getTrackerConnection(config).then(function(tracker) {
        if (tracker) {
            _tracker = tracker;
            return tracker;
        }
        return Promise.reduce(_.range(_self.config.trackers.length), function(total, index) {
            if (_tracker) {
                return _tracker;
            }
            if (index == _self._trackerIndex) {
                return;
            }
            config = _self._getTrackerConfig(index);
            return _getTrackerConnection(config).then(function(tr) {
                _tracker = tr;
            });
        }, 0);
 
    }).then(function() {
        if (_tracker) {
            return _tracker;
        }
        throw new Error('all trackers connect fail, please check your tracker config or your tracker server.');
    });
};
 
FdfsClient.prototype._getTrackerConfig = function(index) {
    var config = this.config.trackers[index];
    config.timeout = this.config.timeout;
    config.charset = this.config.charset;
    return config;
};
 
 
function _getTrackerConnection(config) {
    return new Promise(function(resolve, reject) {
        var _name = config.host + ':' + config.port;
        var socket = new net.Socket();
        logger.debug('connect to tracker server [%s]', _name);
        socket.setTimeout(config.timeout);
        socket.connect(config.port, config.host);
 
        socket.on('error', function(err) {
            logger.error('connect to tracker server [' + _name + '] err:', err);
            resolve();
        });
 
        socket.on('timeout', function() {
            socket.destroy();
            logger.error('connect to tracker server [' + _name + '] timeout.');
            resolve();
        });
 
        socket.on('connect', function() {
            logger.debug('tracker server [%s] is connected', _name);
            resolve(new Tracker(config, socket));
        });
    });
}
 
// public methods
 
/**
 * 上传文件
 * @param file absolute file path or Buffer or ReadableStream
 * @param options
 *      options.group: 指定要上传的group, 不指定则由tracker server分配
 *      options.size: file size, file参数为ReadableStream时必须指定
 *      options.ext: 上传文件的后缀,不指定则获取file参数的后缀,不含(.)
 */
FdfsClient.prototype.upload = function(file, options) {
    var _self = this;
    options = options || {};
    return _normalizeUploadParams(file, options).then(function() {
        if (!options.ext) {
            options.ext = _self.config.defaultExt;
        }
 
        if (!options.group && options.fileId) {
            var gf = helpers.id2gf(options.fileId);
            options.group = gf.group;
        }
        return _self._getTracker();
    }).then(function(tracker) {
        return tracker.getStoreStorage(options.group);
    }).then(function(storage) {
        return storage.upload(file, options);
    });
};
 
/**
 * 下载文件
 * @param fileId
 * @param options options可以直接传options.target
 *      options.target 下载的文件流将被写入到这里,可以是本地文件名,也可以是WritableStream
 *      options.offset和options.bytes: 当只想下载文件中的某1片段时指定
 */
FdfsClient.prototype.download = function(fileId, options) {
    var _self = this;
    return new Promise(function(resolve, reject) {
        if (!options || is.function(options)) {
            reject(new Error('options.target is not specified'));
            return;
        }
 
        // 直接传入target
        if (!options.target) {
            var ori = options;
            options = {};
            options.target = ori;
        }
 
        if (!(is.string(options.target) || is.writableStream(options.target))) {
            reject(new Error('options.target is invalid, it\'s type must be String or WritableStream'));
            return;
        }
 
        if (is.string(options.target)) {
            options.target = fs.createWriteStream(options.target);
        }
        resolve();
    }).then(function() {
        return _self._getTracker();
    }).then(function(tracker) {
        return tracker.getFetchStorage(fileId);
    }).then(function(storage) {
        return storage.download(fileId, options);
    });
};
 
/**
 * 删除fileId指定的文件
 * @param fileId
 */
FdfsClient.prototype.del = function(fileId) {
    return this._getTracker().then(function(tracker) {
        return tracker.getUpdateStorage(fileId);
    }).then(function(storage) {
        return storage.del(fileId);
    });
};
 
FdfsClient.prototype.remove = FdfsClient.prototype.del;
 
/**
 * @param fileId
 * @param metaData  {key1: value1, key2: value2}
 * @param flag 'O' for overwrite all old metadata (default)
 'M' for merge, insert when the meta item not exist, otherwise update it
 */
FdfsClient.prototype.setMetaData = function(fileId, metaData, flag) {
    return this._getTracker().then(function(tracker) {
        return tracker.getUpdateStorage(fileId);
    }).then(function(storage) {
        return storage.setMetaData(fileId, metaData, flag);
    });
};
 
/**
 * 获取指定fileId的meta data
 * @param fileId
 */
FdfsClient.prototype.getMetaData = function(fileId) {
    return this._getTracker().then(function(tracker) {
        return tracker.getUpdateStorage(fileId);
    }).then(function(storage) {
        return storage.getMetaData(fileId);
    });
};
 
/**
 * 获取指定fileId的信息
 * fileInfo会传给回调,结构如下
 *  {
 *      // 文件大小
 *      size:
 *      // 文件创建的UTC时间戳,单位为秒
 *      timestamp:
 *      crc32:
 *      // 最初上传到的storage server的ip
 *      addr:
 *  }
 * @param fileId
 */
FdfsClient.prototype.getFileInfo = function(fileId) {
    return this._getTracker().then(function(tracker) {
        return tracker.getUpdateStorage(fileId);
    }).then(function(storage) {
        return storage.getFileInfo(fileId);
    });
};
 
FdfsClient.prototype.listGroups = function() {
    return this._getTracker().then(function(tracker) {
        return tracker.listGroups();
    });
};
 
FdfsClient.prototype.listStorages = function(group) {
    return this._getTracker().then(function(tracker) {
        return tracker.listStorages(group);
    });
};
 
// helpers
/**
 * 验证file参数是否合法,同时补充一些必要的参数
 * 若为String,则需验证是否存在
 * 若为ReadableStream,则需验证options.size是否存在
 * @param file
 * @param options
 */
function _normalizeUploadParams(file, options) {
    return new Promise(function(resolve, reject) {
        if (!file) {
            reject(new Error('The "file" parameter is empty.'));
            return;
        }
 
        if (!(is.string(file) || is.buffer(file) || is.readableStream(file))) {
            reject(new Error('The "file" parameter is invalid, it must be a String, Buffer, or ReadableStream'));
            return;
        }
 
 
        if (is.readableStream(file) && !options.size) {
            reject(new Error('when the "file" parameter\'s is ReadableStream, options.size must specified'));
            return;
        }
 
        if (is.buffer(file)) {
            options.size = file.length;
        }
 
        if (is.string(options.size)) {
            options.size = Number(options.size);
        }
 
        // TODO
        if (options.method === protocol.FDFS_METHOD_UPLOAD_APPENDER_FILE) {
 
        } else if (options.method === protocol.FDFS_METHOD_APPEND_FILE) {
            if (!options.fileId) {
                reject(new Error('options.fileId is missed'));
            }
        } else if (options.method === protocol.FDFS_METHOD_MODIFY_FILE) {
            if (!options.fileId) {
                reject(new Error('options.fileId is missed'));
            }
 
            if (is.string(options.offset)) {
                options.offset = Number(options.offset);
            }
 
            if (!(options.offset || options.offset === 0)) {
                reject(new Error('options.offset is missed'));
            }
            if (!is.number(options.offset)) {
                reject(new Error('options.offset must be a number'));
            }
        }
 
        if (is.string(file)) {
            var stats = fs.statSync(file);
            options.size = stats.size;
            if (!options.ext) {
                options.ext = path.extname(file);
                if (options.ext) {
                    // 去掉.
                    options.ext = options.ext.substring(1);
                }
            }
            resolve();
        }
        resolve();
    });
 
}