Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. var fs = require('fs'),
  2. EventEmitter = require('events').EventEmitter,
  3. inherits = require('util').inherits,
  4. FTP = require('ftp'),
  5. _ = require('lodash'),
  6. glob = require('glob'),
  7. async = require('async'),
  8. Client,
  9. MAX_CONNECTIONS = 10,
  10. logging = 'basic',
  11. loggingLevels = ['none', 'basic', 'debug'],
  12. log = function (msg, lvl) {
  13. if (loggingLevels.indexOf(lvl) <= logging) {
  14. console.log(msg);
  15. }
  16. };
  17. Client = module.exports = function (config, options) {
  18. if (!(this instanceof Client))
  19. return new Client();
  20. this.config = _.defaults(config || {}, {
  21. host: 'localhost',
  22. port: 21,
  23. user: 'anonymous',
  24. password: 'anonymous@'
  25. });
  26. this.options = _.defaults(options || {}, {
  27. overwrite: 'older' // | 'all' | 'none'
  28. });
  29. if (this.options.logging) {
  30. logging = this.options.logging;
  31. logging = loggingLevels.indexOf(logging);
  32. }
  33. this.ftp = new FTP();
  34. this.ftp.on('error', function (err) {
  35. throw new Error(err);
  36. });
  37. };
  38. inherits(Client, EventEmitter);
  39. Client.prototype.connect = function (callback) {
  40. this.ftp.on('ready', function () {
  41. log('Connected to ' + this.config.host, 'debug');
  42. log('Checking server local time...', 'debug');
  43. this._checkTimezone(function () {
  44. this.emit('ready');
  45. if (typeof callback !== 'undefined') {
  46. callback();
  47. }
  48. }.bind(this));
  49. }.bind(this));
  50. this.ftp.connect(this.config || {});
  51. };
  52. Client.prototype.upload = function (patterns, dest, options, uploadCallback) {
  53. options = _.defaults(options || {}, this.options);
  54. var paths, files, dirs, toDelete = [], ftp = this.ftp;
  55. paths = this._glob(patterns);
  56. paths = this._clean(paths, options.baseDir);
  57. paths = this._stat(paths);
  58. files = paths[1];
  59. dirs = paths[0];
  60. var sources = function (array) {
  61. array.forEach(function (file) {
  62. log(file.src, 'debug');
  63. });
  64. }
  65. log('FILES TO UPLOAD', 'debug');
  66. sources(files);
  67. log('DIRS TO UPLOAD', 'debug');
  68. sources(dirs);
  69. var deleteFiles = function (cb) {
  70. async.eachLimit(toDelete, MAX_CONNECTIONS, function (file, callback) {
  71. var destPath = (file.src.indexOf(options.baseDir) === 0 ?
  72. file.src.substring(options.baseDir.length + 1) : file.src);
  73. log('Deleting ' + destPath, 'debug');
  74. if (file.isDirectory()) {
  75. ftp.rmdir(destPath, function (err) {
  76. if (err) log(err, 'debug');
  77. callback();
  78. }.bind(file));
  79. } else {
  80. ftp.delete(destPath, function (err) {
  81. if (err) log(err, 'debug');
  82. callback();
  83. }.bind(file));
  84. }
  85. }, cb);
  86. },
  87. uploadFiles = function (cb) {
  88. async.eachLimit(files, MAX_CONNECTIONS, function (file, callback) {
  89. var destPath = (file.src.indexOf(options.baseDir) === 0 ?
  90. file.src.substring(options.baseDir.length + 1) : file.src);
  91. log('Uploading file ' + destPath, 'debug');
  92. ftp.put(file.src, destPath, function (err) {
  93. if (err) {
  94. log('Error uploading file ' + destPath + ': ' + err, 'basic');
  95. this.uploaded = false;
  96. this.error = err;
  97. } else {
  98. log('Finished uploading file ' + destPath, 'basic');
  99. this.uploaded = true;
  100. }
  101. callback();
  102. }.bind(file));
  103. }, cb);
  104. },
  105. uploadDirs = function (cb) {
  106. async.eachLimit(dirs, MAX_CONNECTIONS, function (dir, callback) {
  107. var destPath = (dir.src.indexOf(options.baseDir) === 0 ?
  108. dir.src.substring(options.baseDir.length + 1) : dir.src);
  109. log('Uploading directory ' + destPath, 'debug');
  110. ftp.mkdir(destPath, function (err) {
  111. if (err) {
  112. log('Error uploading directory ' + destPath + ': ' + err, 'basic');
  113. this.uploaded = false;
  114. this.error = err;
  115. } else {
  116. log('Finished uploading directory ' + destPath, 'basic');
  117. this.uploaded = true;
  118. }
  119. callback();
  120. }.bind(dir))
  121. }, cb);
  122. },
  123. compare = function (cb) {
  124. var timeDif = this.serverTimeDif;
  125. if (options.overwrite === 'all') {
  126. toDelete = files.concat(dirs);
  127. cb();
  128. } else {
  129. async.eachLimit(files.concat(dirs), MAX_CONNECTIONS, function (file, callback) {
  130. var destPath = (file.src.indexOf(options.baseDir) === 0 ?
  131. file.src.substring(options.baseDir.length + 1) : file.src);
  132. ftp.list(destPath, function (err, list) {
  133. if (err) log(err, 'debug');
  134. log('Comparing file' + this.src, 'debug');
  135. if (list && list[0]) {
  136. if (options.overwrite === 'older' && list[0].date && new Date(list[0].date.getTime() + timeDif) < this.mtime) {
  137. toDelete.push(this);
  138. } else {
  139. if (this.isDirectory()) {
  140. dirs.forEach(function (dir, i) {
  141. if (dir.src === this.src) {
  142. dirs.splice(i, 1);
  143. }
  144. }.bind(this))
  145. } else {
  146. files.forEach(function (file, i) {
  147. if (file.src === this.src) {
  148. files.splice(i, 1);
  149. }
  150. }.bind(this))
  151. }
  152. }
  153. }
  154. callback();
  155. }.bind(file))
  156. }, cb);
  157. }
  158. }.bind(this)
  159. this._cwd(dest, function () {
  160. log('Moved to directory ' + dest, 'debug');
  161. var tasks = [];
  162. // collect files and dirs to be deleted
  163. tasks.push(function (callback) {
  164. log('1. Compare files', 'debug');
  165. return compare(function (err) {
  166. if (err) log(err, 'debug');
  167. log('FILES TO DELETE', 'debug');
  168. sources(toDelete);
  169. log('Found ' + files.length + ' files and ' + dirs.length + ' directories to upload.', 'basic');
  170. callback();
  171. }.bind(this));
  172. }.bind(this));
  173. // delete files and dirs
  174. tasks.push(function (callback) {
  175. log('2. Delete files', 'debug');
  176. return deleteFiles(function (err) {
  177. if (err) log(err, 'debug');
  178. callback();
  179. }.bind(this));
  180. }.bind(this));
  181. // upload dirs
  182. tasks.push(function (callback) {
  183. log('3. Upload dirs', 'debug');
  184. return uploadDirs(function (err) {
  185. if (err) log(err, 'debug');
  186. else log('Uploaded dirs', 'debug');
  187. callback();
  188. }.bind(this));
  189. }.bind(this));
  190. // upload files
  191. tasks.push(function (callback) {
  192. log('4. Upload files', 'debug');
  193. return uploadFiles(function (err) {
  194. if (err) log(err, 'debug');
  195. else log('Uploaded files', 'debug');
  196. callback();
  197. }.bind(this));
  198. }.bind(this));
  199. async.series(tasks, function (err) {
  200. if (err) throw err;
  201. ftp.end();
  202. log('Upload done', 'debug');
  203. var result = {
  204. uploadedFiles: [],
  205. uploadedDirs: [],
  206. errors: {}
  207. }
  208. dirs.forEach(function (dir) {
  209. if (dir.uploaded) {
  210. result.uploadedDirs.push(dir.src);
  211. } else {
  212. result.errors[dir.src] = dir.error;
  213. }
  214. });
  215. files.forEach(function (file) {
  216. if (file.uploaded) {
  217. result.uploadedFiles.push(file.src);
  218. } else {
  219. result.errors[file.src] = file.error;
  220. }
  221. })
  222. log('Finished uploading ' + result.uploadedFiles.length + ' of ' + files.length + ' files.', 'basic');
  223. uploadCallback(result);
  224. });
  225. }.bind(this));
  226. }
  227. Client.prototype.download = function (source, dest, options, downloadCallback) {
  228. options = _.defaults(options || {}, this.options);
  229. if (!fs.existsSync(dest)) {
  230. this.ftp.end();
  231. throw new Error('The download destination directory ' + dest + ' does not exist.');
  232. }
  233. var ftp = this.ftp;
  234. var timeDif = this.serverTimeDif;
  235. var files = {}, dirs = [];
  236. var queue = async.queue(function (task, callback) {
  237. log('Queue worker started for ' + task.src, 'debug');
  238. ftp.list(task.src, function (err, list) {
  239. if (err || typeof list === 'undefined' || typeof list[0] === 'undefined') {
  240. throw new Error('The source directory on the server ' + task.src + ' does not exist.');
  241. }
  242. if (list && list.length > 1) {
  243. _.each(list.splice(1, list.length - 1), function (file) {
  244. if (file.name !== '.' && file.name !== '..') {
  245. var filename = task.src + '/' + file.name;
  246. if (file.type === 'd') {
  247. dirs.push(filename);
  248. queue.push({src: filename}, function (err) {
  249. if (err) log(err, 'debug');
  250. });
  251. } else if (file.type === '-') {
  252. files[filename] = {
  253. date: file.date
  254. };
  255. }
  256. }
  257. });
  258. }
  259. callback();
  260. });
  261. }, MAX_CONNECTIONS);
  262. queue.drain = function () {
  263. log([dirs, files], 'debug');
  264. dirs.forEach(function (dir) {
  265. var dirName = dest + '/' + (dir.indexOf(source) === 0 ? dir.substring(source.length + 1) : dir);
  266. if (!fs.existsSync(dirName)) {
  267. fs.mkdirSync(dirName);
  268. log('Created directory ' + dirName, 'debug');
  269. }
  270. });
  271. var toDelete = [], result = {
  272. downloadedFiles: [],
  273. errors: {}
  274. };
  275. if (options.overwrite === 'all') {
  276. toDelete = _.keys(files);
  277. }
  278. if (options.overwrite === 'older') {
  279. var skip = [];
  280. _.each(files, function (details, file) {
  281. var fileName = file.replace(source, dest);
  282. log('Comparing file ' + fileName, 'debug');
  283. if (fs.existsSync(fileName)) {
  284. var stat = fs.statSync(fileName);
  285. if (stat.mtime.getTime() < details.date.getTime() + timeDif) {
  286. toDelete.push(fileName);
  287. } else {
  288. skip.push(file);
  289. }
  290. }
  291. });
  292. skip.forEach(function (file) {
  293. delete files[file];
  294. });
  295. }
  296. if (options.overwrite === 'none') {
  297. var skip = [];
  298. _.each(files, function (details, file) {
  299. var fileName = file.replace(source, dest);
  300. if (fs.existsSync(fileName)) {
  301. skip.push(file);
  302. }
  303. });
  304. skip.forEach(function (file) {
  305. delete files[file];
  306. });
  307. }
  308. toDelete.forEach(function (file) {
  309. try {
  310. fs.unlinkSync(file.replace(source, dest));
  311. } catch (e) {
  312. }
  313. });
  314. log('Found ' + _.keys(files).length + ' files to download.', 'basic');
  315. async.forEachLimit(_.keys(files), MAX_CONNECTIONS, function (file, callback) {
  316. log('Downloading file ' + file, 'debug');
  317. ftp.get(file, function (err, stream) {
  318. if (err && err.message !== 'Unable to make data connection') {
  319. log('Error downloading file ' + file, 'basic');
  320. result['errors'][file] = err;
  321. }
  322. if (stream) {
  323. stream.once('close', function () {
  324. log('Finished downloading file ' + file, 'basic');
  325. result['downloadedFiles'].push(file);
  326. callback();
  327. });
  328. stream.pipe(fs.createWriteStream(file.replace(source, dest)));
  329. }
  330. });
  331. }, function (err) {
  332. if (err) return next(err);
  333. if (downloadCallback) {
  334. downloadCallback(result);
  335. }
  336. log('Finished downloading ' + result.downloadedFiles.length + ' of ' + _.keys(files).length + ' files', 'basic');
  337. ftp.end();
  338. });
  339. log(['To delete: ', toDelete], 'debug');
  340. log(['To download: ', files], 'debug');
  341. }
  342. queue.push({src: source}, function (err) {
  343. if (err) log(err, 'debug');
  344. });
  345. // 1. check if directory exists
  346. // 2. if not throw an error
  347. // 3. if it does - build a list of directories and files using async.queue
  348. // 4. download all the files from the list
  349. }
  350. Client.prototype._cwd = function (path, callback) {
  351. this.ftp.mkdir(path, true, function (err) {
  352. if (err) log(err, 'debug');
  353. this.ftp.cwd(path, function (err) {
  354. if (err) log(err, 'debug');
  355. callback();
  356. });
  357. }.bind(this));
  358. }
  359. Client.prototype._checkTimezone = function (cb) {
  360. var localTime = new Date().getTime(),
  361. serverTime,
  362. ftp = this.ftp;
  363. async.series([
  364. function (next) {
  365. return ftp.put(new Buffer(''), '.timestamp', function (err) {
  366. if (err) log(err, 'debug');
  367. next();
  368. });
  369. },
  370. function (next) {
  371. return ftp.list('.timestamp', function (err, list) {
  372. if (err) log(err, 'debug');
  373. if (list && list[0] && list[0].date) {
  374. serverTime = list[0].date.getTime();
  375. }
  376. next();
  377. });
  378. },
  379. function (next) {
  380. return ftp.delete('.timestamp', function (err) {
  381. if (err) log(err, 'debug');
  382. next();
  383. });
  384. }
  385. ], function () {
  386. this.serverTimeDif = localTime - serverTime;
  387. log('Server time is ' + new Date(new Date().getTime() - this.serverTimeDif), 'debug');
  388. cb();
  389. }.bind(this));
  390. }
  391. Client.prototype._glob = function (patterns) {
  392. var include = [],
  393. exclude = [];
  394. if (!_.isArray(patterns)) {
  395. patterns = [patterns];
  396. }
  397. patterns.forEach(function (pattern) {
  398. if (pattern.indexOf('!') === 0) {
  399. exclude = exclude.concat(glob.sync(pattern.substring(1), {nonull: false}) || []);
  400. } else {
  401. include = include.concat(glob.sync(pattern, {nonull: false}) || []);
  402. }
  403. });
  404. return _.difference(include, exclude);
  405. }
  406. Client.prototype._stat = function (files) {
  407. var result = [
  408. [],
  409. []
  410. ];
  411. _.each(files, function (file) {
  412. file = _.extend(fs.statSync(file), {src: file});
  413. if (file.isDirectory()) {
  414. result[0].push(file);
  415. } else {
  416. result[1].push(file);
  417. }
  418. });
  419. return result;
  420. }
  421. Client.prototype._clean = function (files, baseDir) {
  422. if (!baseDir) {
  423. return files;
  424. }
  425. return _.compact(_.map(files, function (file) {
  426. if (file.replace(baseDir, '')) {
  427. return file;
  428. } else {
  429. return null;
  430. }
  431. }));
  432. }