<commit_before>(function() {
	"use strict";

	var	MarkdownIt = require('markdown-it'),
		fs = require('fs'),
		path = require('path'),
		url = require('url');

	var	meta = module.parent.require('./meta'),
		nconf = module.parent.require('nconf'),
		translator = module.parent.require('../public/src/modules/translator'),
		plugins = module.parent.exports;

	var	parser,
		Markdown = {
			config: {},
			onLoad: function(params, callback) {
				function render(req, res, next) {
					res.render('admin/plugins/markdown', {
						themes: Markdown.themes
					});
				}

				params.router.get('/admin/plugins/markdown', params.middleware.admin.buildHeader, render);
				params.router.get('/api/admin/plugins/markdown', render);

				Markdown.init();
				Markdown.loadThemes();

				callback();
			},

			getConfig: function(config, callback) {
				config.markdown = {
					highlight: Markdown.highlight ? 1 : 0,
					theme: Markdown.config.highlightTheme || 'railscasts.css'
				};
				callback(null, config);
			},

			getLinkTags: function(links, callback) {
				links.push({
					rel: "stylesheet",
					type: "",
					href: nconf.get('relative_path') + '/plugins/nodebb-plugin-markdown/styles/' + (Markdown.config.highlightTheme || 'railscasts.css')
				});
				callback(null, links);
			},

			init: function() {
				// Load saved config
				var	_self = this,
					fields = [
						'html', 'xhtmlOut', 'breaks', 'langPrefix', 'linkify', 'typographer', 'externalBlank', 'nofollow'
					],
					defaults = {
						'html': false,
						'xhtmlOut': true,
						'breaks': true,
						'langPrefix': 'language-',
						'linkify': true,
						'typographer': false,
						'highlight': true,
						'highlightTheme': 'railscasts.css',
						'externalBlank': false,
						'nofollow': true
					};

				meta.settings.get('markdown', function(err, options) {
					for(var field in defaults) {
						// If not set in config (nil)
						if (!options.hasOwnProperty(field)) {
							_self.config[field] = defaults[field];
						} else {
							if (field !== 'langPrefix' && field !== 'highlightTheme' && field !== 'headerPrefix') {
								_self.config[field] = options[field] === 'on' ? true : false;
							} else {
								_self.config[field] = options[field];
							}
						}
					}

					_self.highlight = _self.config.highlight;
					delete _self.config.highlight;

					parser = new MarkdownIt(_self.config);

					Markdown.updateParserRules(parser);
				});
			},

			loadThemes: function() {
				fs.readdir(path.join(__dirname, 'public/styles'), function(err, files) {
					var isStylesheet = /\.css$/;
					Markdown.themes = files.filter(function(file) {
						return isStylesheet.test(file);
					}).map(function(file) {
						return {
							name: file
						}
					});
				});
			},

			parsePost: function(data, callback) {
				if (data && data.postData && data.postData.content && parser) {
					data.postData.content = parser.render(data.postData.content);
				}
				callback(null, data);
			},

			parseSignature: function(data, callback) {
				if (data && data.userData && data.userData.signature && parser) {
					data.userData.signature = parser.render(data.userData.signature);
				}
				callback(null, data);
			},

			parseAboutMe: function(aboutme, callback) {
				callback(null, (aboutme && parser) ? parser.render(aboutme) : aboutme);
			},

			parseRaw: function(raw, callback) {
				callback(null, (raw && parser) ? parser.render(raw) : raw);
			},
			renderHelp: function(helpContent, callback) {
				translator.translate('[[markdown:help_text]]', function(translated) {
					plugins.fireHook('filter:parse.raw', '## Markdown\n' + translated, function(err, parsed) {
						helpContent += parsed;
						callback(null, helpContent);
					});
				});
			},

			registerFormatting: function(payload, callback) {
				var formatting = [
					{name: 'bold', className: 'fa fa-bold', title: '[[modules:composer.formatting.bold]]'},
					{name: 'italic', className: 'fa fa-italic', title: '[[modules:composer.formatting.italic]]'},
					{name: 'list', className: 'fa fa-list', title: '[[modules:composer.formatting.list]]'},
					{name: 'strikethrough', className: 'fa fa-strikethrough', title: '[[modules:composer.formatting.strikethrough]]'},
					{name: 'link', className: 'fa fa-link', title: '[[modules:composer.formatting.link]]'},
					{name: 'picture-o', className: 'fa fa-picture-o', title: '[[modules:composer.formatting.picture]]'}
				];

				payload.options = formatting.concat(payload.options);

				callback(null, payload);
			},

			updateParserRules: function(parser) {
				// Update renderer to add some classes to all images
				var renderImage = parser.renderer.rules.image || function(tokens, idx, options, env, self) {
						return self.renderToken.apply(self, arguments);
					},
					renderLink = parser.renderer.rules.link_open || function(tokens, idx, options, env, self) {
						return self.renderToken.apply(self, arguments);
					};

				parser.renderer.rules.image = function (tokens, idx, options, env, self) {
					var classIdx = tokens[idx].attrIndex('class'),
						srcIdx = tokens[idx].attrIndex('src');

					// Validate the url
					if (!Markdown.isUrlValid(tokens[idx].attrs[srcIdx][1])) { return ''; }

					if (classIdx < 0) {
						tokens[idx].attrPush(['class', 'img-responsive img-markdown']);
					} else {
						tokens[idx].attrs[classIdx][1] = tokens[idx].attrs[classIdx][1] + ' img-responsive img-markdown';
					}

					return renderImage(tokens, idx, options, env, self);
				};

				parser.renderer.rules.link_open = function(tokens, idx, options, env, self) {
					// Add target="_blank" to all links
					var targetIdx = tokens[idx].attrIndex('target'),
						relIdx = tokens[idx].attrIndex('rel'),
						hrefIdx = tokens[idx].attrIndex('href');

					if (Markdown.isExternalLink(tokens[idx].attrs[hrefIdx][1])) {
						if (Markdown.config.externalBlank) {
							if (targetIdx < 0) {
								tokens[idx].attrPush(['target', '_blank']);
							} else {
								tokens[idx].attrs[targetIdx][1] = '_blank';
							}
						}

						if (Markdown.config.nofollow) {
							if (relIdx < 0) {
								tokens[idx].attrPush(['rel', 'nofollow']);
							} else {
								tokens[idx].attrs[relIdx][1] = 'nofollow';
							}
						}
					}

					return renderLink(tokens, idx, options, env, self);
				};

				plugins.fireHook('action:markdown.updateParserRules', parser);
			},

			isUrlValid: function(src) {
				try {
					var urlObj = url.parse(src, false, true);
					if (urlObj.host === null && !urlObj.pathname.toString().startsWith(nconf.get('relative_path') + nconf.get('upload_url'))) {
						return false;
					} else {
						return true;
					}
				} catch (e) {
					return false;
				}
			},

			isExternalLink: function(urlString) {
				var urlObj = url.parse(urlString),
					baseUrlObj = url.parse(nconf.get('url'));

				if (
					urlObj.host === null ||	// Relative paths are always internal links...
					(urlObj.host === baseUrlObj.host && urlObj.protocol === baseUrlObj.protocol &&	// Otherwise need to check that protocol and host match
					(nconf.get('relative_path').length > 0 ? urlObj.pathname.indexOf(nconf.get('relative_path')) === 0 : true))	// Subfolder installs need this additional check
				) {
					return false;
				} else {
					return true;
				}
			},

			admin: {
				menu: function(custom_header, callback) {
					custom_header.plugins.push({
						"route": '/plugins/markdown',
						"icon": 'fa-edit',
						"name": 'Markdown'
					});

					callback(null, custom_header);
				}
			}
		};

	module.exports = Markdown;
})();

<commit_msg>'Rebranded' Markdown as BBCodeParser<commit_after>(function() {
	"use strict";

	var	fs = require('fs'),
		path = require('path'),
		url = require('url');

	var	meta = module.parent.require('./meta'),
		nconf = module.parent.require('nconf'),
		translator = module.parent.require('../public/src/modules/translator'),
		plugins = module.parent.exports;

	var	parser,
		BBCodeParser = {
			config: {},
			onLoad: function(params, callback) {
				function render(req, res, next) {
					res.render('admin/plugins/bbcode-parser', {
						themes: BBCodeParser.themes
					});
				}

				params.router.get('/admin/plugins/bbcode-parser', params.middleware.admin.buildHeader, render);
				params.router.get('/api/admin/plugins/bbcode-parser', render);

				BBCodeParser.init();
				BBCodeParser.loadThemes();

				callback();
			},

			getConfig: function(config, callback) {
				config['bbcode-parser'] = {
					highlight: BBCodeParser.highlight ? 1 : 0,
					theme: BBCodeParser.config.highlightTheme || 'railscasts.css'
				};
				callback(null, config);
			},

			getLinkTags: function(links, callback) {
				links.push({
					rel: "stylesheet",
					type: "",
					href: nconf.get('relative_path') + '/plugins/nodebb-plugin-bbcode-parser/styles/' + (BBCodeParser.config.highlightTheme || 'railscasts.css')
				});
				callback(null, links);
			},

			init: function() {
				// Load saved config
				var	_self = this,
					fields = [
						'html', 'xhtmlOut', 'breaks', 'langPrefix', 'linkify', 'typographer', 'externalBlank', 'nofollow'
					],
					defaults = {
						'html': false,
						'xhtmlOut': true,
						'breaks': true,
						'langPrefix': 'language-',
						'linkify': true,
						'typographer': false,
						'highlight': true,
						'highlightTheme': 'railscasts.css',
						'externalBlank': false,
						'nofollow': true
					};

				meta.settings.get('bbcode-parser', function(err, options) {
					for(var field in defaults) {
						// If not set in config (nil)
						if (!options.hasOwnProperty(field)) {
							_self.config[field] = defaults[field];
						} else {
							if (field !== 'langPrefix' && field !== 'highlightTheme' && field !== 'headerPrefix') {
								_self.config[field] = options[field] === 'on' ? true : false;
							} else {
								_self.config[field] = options[field];
							}
						}
					}

					_self.highlight = _self.config.highlight;
					delete _self.config.highlight;

					parser = require('./tags.js')(_self.config);
				});
			},

			loadThemes: function() {
				fs.readdir(path.join(__dirname, 'public/styles'), function(err, files) {
					var isStylesheet = /\.css$/;
					BBCodeParser.themes = files.filter(function(file) {
						return isStylesheet.test(file);
					}).map(function(file) {
						return {
							name: file
						}
					});
				});
			},

			parsePost: function(data, callback) {
				if (data && data.postData && data.postData.content && parser) {
					data.postData.content = parser.render(data.postData.content);
				}
				callback(null, data);
			},

			parseSignature: function(data, callback) {
				if (data && data.userData && data.userData.signature && parser) {
					data.userData.signature = parser.render(data.userData.signature);
				}
				callback(null, data);
			},

			parseAboutMe: function(aboutme, callback) {
				callback(null, (aboutme && parser) ? parser.render(aboutme) : aboutme);
			},

			parseRaw: function(raw, callback) {
				callback(null, (raw && parser) ? parser.render(raw) : raw);
			},
			renderHelp: function(helpContent, callback) {
				translator.translate('[[bbcode-parser:help_text]]', function(translated) {
					plugins.fireHook('filter:parse.raw', '## BBCodeParser\n' + translated, function(err, parsed) {
						helpContent += parsed;
						callback(null, helpContent);
					});
				});
			},

			registerFormatting: function(payload, callback) {
				var formatting = [
					{name: 'bold', className: 'fa fa-bold', title: '[[modules:composer.formatting.bold]]'},
					{name: 'italic', className: 'fa fa-italic', title: '[[modules:composer.formatting.italic]]'},
					{name: 'list', className: 'fa fa-list', title: '[[modules:composer.formatting.list]]'},
					{name: 'strikethrough', className: 'fa fa-strikethrough', title: '[[modules:composer.formatting.strikethrough]]'},
					{name: 'link', className: 'fa fa-link', title: '[[modules:composer.formatting.link]]'},
					{name: 'picture-o', className: 'fa fa-picture-o', title: '[[modules:composer.formatting.picture]]'}
				];

				payload.options = formatting.concat(payload.options);

				callback(null, payload);
			},

			updateParserRules: function(parser) {
				// Update renderer to add some classes to all images
				var renderImage = parser.renderer.rules.image || function(tokens, idx, options, env, self) {
						return self.renderToken.apply(self, arguments);
					},
					renderLink = parser.renderer.rules.link_open || function(tokens, idx, options, env, self) {
						return self.renderToken.apply(self, arguments);
					};

				parser.renderer.rules.image = function (tokens, idx, options, env, self) {
					var classIdx = tokens[idx].attrIndex('class'),
						srcIdx = tokens[idx].attrIndex('src');

					// Validate the url
					if (!BBCodeParser.isUrlValid(tokens[idx].attrs[srcIdx][1])) { return ''; }

					if (classIdx < 0) {
						tokens[idx].attrPush(['class', 'img-responsive img-markdown']);
					} else {
						tokens[idx].attrs[classIdx][1] = tokens[idx].attrs[classIdx][1] + ' img-responsive img-markdown';
					}

					return renderImage(tokens, idx, options, env, self);
				};

				parser.renderer.rules.link_open = function(tokens, idx, options, env, self) {
					// Add target="_blank" to all links
					var targetIdx = tokens[idx].attrIndex('target'),
						relIdx = tokens[idx].attrIndex('rel'),
						hrefIdx = tokens[idx].attrIndex('href');

					if (BBCodeParser.isExternalLink(tokens[idx].attrs[hrefIdx][1])) {
						if (BBCodeParser.config.externalBlank) {
							if (targetIdx < 0) {
								tokens[idx].attrPush(['target', '_blank']);
							} else {
								tokens[idx].attrs[targetIdx][1] = '_blank';
							}
						}

						if (BBCodeParser.config.nofollow) {
							if (relIdx < 0) {
								tokens[idx].attrPush(['rel', 'nofollow']);
							} else {
								tokens[idx].attrs[relIdx][1] = 'nofollow';
							}
						}
					}

					return renderLink(tokens, idx, options, env, self);
				};

				plugins.fireHook('action:bbcode-parser.updateParserRules', parser);
			},

			isUrlValid: function(src) {
				try {
					var urlObj = url.parse(src, false, true);
					if (urlObj.host === null && !urlObj.pathname.toString().startsWith(nconf.get('relative_path') + nconf.get('upload_url'))) {
						return false;
					} else {
						return true;
					}
				} catch (e) {
					return false;
				}
			},

			isExternalLink: function(urlString) {
				var urlObj = url.parse(urlString),
					baseUrlObj = url.parse(nconf.get('url'));

				if (
					urlObj.host === null ||	// Relative paths are always internal links...
					(urlObj.host === baseUrlObj.host && urlObj.protocol === baseUrlObj.protocol &&	// Otherwise need to check that protocol and host match
					(nconf.get('relative_path').length > 0 ? urlObj.pathname.indexOf(nconf.get('relative_path')) === 0 : true))	// Subfolder installs need this additional check
				) {
					return false;
				} else {
					return true;
				}
			},

			admin: {
				menu: function(custom_header, callback) {
					custom_header.plugins.push({
						"route": '/plugins/bbcode-parser',
						"icon": 'fa-edit',
						"name": 'BBCode Parser'
					});

					callback(null, custom_header);
				}
			}
		};

	module.exports = BBCodeParser;
})();

<|endoftext|>


<commit_before>/* eslint-env node */
'use strict';

const BasePlugin = require('ember-cli-deploy-plugin');
const RSVP = require('rsvp');
const fs = require('fs-extra');
const path = require('path');
const archiver = require('archiver');
const AWS = require('aws-sdk');

const DEFAULT_DEPLOY_INFO = 'fastboot-deploy-info.json';
const DEFAULT_DEPLOY_ARCHIVE = 'dist';

module.exports = {
  name: 'ember-cli-deploy-fastboot-s3',

  createDeployPlugin(options) {
    const name = options.name;
    const DeployPlugin = BasePlugin.extend({
      name,
      defaultConfig: {
        archivePath: path.join('tmp', DEFAULT_DEPLOY_ARCHIVE),
        archiveType: 'zip',
        deployInfo: DEFAULT_DEPLOY_INFO,
        deployArchive: DEFAULT_DEPLOY_ARCHIVE,
        distDir: (context) => context.distDir,
        revisionKey: (context) => (
          context.commandOptions.revision ||
          (context.revisionData && context.revisionData.revisionKey)
        ),
        s3Client: (context) => context.s3Client,
      },

      requiredConfig: ['bucket'],

      configure(/*context*/) {
        // Ensure default config is applied
        this._super.configure.apply(this, arguments);

        // If a custom S3 client is configured then the rest of the
        // configuration is redundant.
        if (this.readConfig('s3Client')) {
          return;
        }

        // An endpoints makes the region config redundant, however
        // at least one of them must be present.
        if (!this.readConfig('region') && !this.readConfig('endpoint')) {
          const message = `You must configure either an 'endpoint' or a 'region' to use the AWS.S3 client.`;

          this.log(message, { color: 'red' });
          throw new Error(message);
        }
      },

      setup(/*context*/) {
        this.s3 =
          this.readConfig('s3Client') ||
          new AWS.S3({
            region: this.readConfig('region'),
            accessKeyId: this.readConfig('accessKeyId'),
            secretAccessKey: this.readConfig('secretAccessKey'),
            endpoint: this.readConfig('endpoint')
          });
      },

      didPrepare(/*context*/) {
        return this._pack()
          .then(() => {
            const archiveName = this._buildArchiveName();
            this.log(`✔  ${archiveName}`, { verbose: true });
          });
      },

      upload(/*context*/) {
        const prefix = this.readConfig('prefix');
        const archiveName = this._buildArchiveName();
        this.key = prefix ? [prefix, archiveName].join('/') : archiveName;

        return this._upload(this.s3)
          .then(() => {
            this.log(`✔  ${this.key}`, { verbose: true });
            return RSVP.Promise.resolve();
          })
          .catch(this._errorMessage.bind(this));
      },

      activate(context) {
        const revisionKey = this.readConfig('revisionKey');

        this.log(`preparing to activate ${revisionKey}`, {
          verbose: true
        });

        let _this = this;
        return this.fetchRevisions(context).then(function(revisions) {
           let found = revisions.revisions.map(function(element) { return element.revision; }).indexOf(revisionKey);
           if (found >= 0) {

             return _this._uploadDeployInfo(_this.s3)
               .then(() => {
                 _this.log(`✔  activated revison ${revisionKey}`, {
                   verbose: true
                 });
               })
               .catch(_this._errorMessage.bind(_this));

           } else {
             return RSVP.reject("REVISION NOT FOUND!"); // see how we should handle a pipeline failure
           }
         });
      },

      didDeploy(context) {
        const revisionKey = context.revisionData && context.revisionData.revisionKey;
        const activatedRevisionKey = context.revisionData && context.revisionData.activatedRevisionKey;
        if (revisionKey && !activatedRevisionKey) {
          this.log("Deployed but did not activate revision " + revisionKey + ". "
               + "To activate, run: "
               + "ember deploy:activate " + context.deployTarget + " --revision=" + revisionKey + "\n"
             );
        }
      },

      fetchRevisions(context) {
        return this._list(context)
          .then(function(revisions) {
            return {
              revisions: revisions
            };
          });
      },

      _list(/* context */) {
        const bucket = this.readConfig('bucket');
        const deployArchive = this.readConfig('deployArchive');
        const archiveExt = `.${this.readConfig('archiveType')}`;
        const deployInfo = this.readConfig('deployInfo');
        const prefix = this.readConfig('prefix');
        const archivePath = prefix ? [prefix, deployArchive].join('/') : deployArchive;
        const indexKey = prefix ? [prefix, deployInfo].join('/') : deployInfo;

        let revisionPrefix = `${archivePath}-`;

        return RSVP.hash({
          revisions: this.listObjects(this.s3, { Bucket: bucket, Prefix: revisionPrefix }),
          current: this.getObject(this.s3, { Bucket: bucket, Key: indexKey }),
        })
        .then(function(data) {
          let activeRevision = '';
          if (data.current) {
            let objectData = data.current.Body.toString('utf-8');
            if (objectData[0] === '{') {
              let obj = JSON.parse(objectData);
              if (obj.key) {
                activeRevision = obj.key.substring(revisionPrefix.length, obj.key.lastIndexOf('.'));
              }
            }
          }

          let results = data.revisions.Contents.sort(function(a, b) {
            return new Date(b.LastModified) - new Date(a.LastModified);
          }).map(function(d) {
            let revision = '';
            /* Check that this is the type of configured archive. */
            if (d.Key.lastIndexOf(archiveExt) !== -1) {
              revision = d.Key.substring(revisionPrefix.length, d.Key.lastIndexOf('.'));
            }
            let active = data.current && revision === activeRevision;
            return { revision: revision, timestamp: d.LastModified, active: active, deployer: 'fastboot-s3' };
          }).filter(function(d) {
            /* Filter out results where revision is empty. */
            return d.revision !== '';
          });

          return results;
        }).catch(this._errorMessage.bind(this));
      },

      listObjects(s3, params) {
        return new RSVP.Promise(function(resolve, reject) {
          s3.listObjects(params, function(err, data) {
            if (err) {
              return reject(err);
            }
            return resolve(data);
          });
        });
      },

      getObject(s3, params) {
        return new RSVP.Promise(function(resolve, reject) {
          s3.getObject(params, function(err, data) {
            if (err && (err.code === 'NotFound' || err.code === 'NoSuchKey')) {
              return resolve();
            }
            else if (err) {
              return reject(err);
            }
            else {
              return resolve(data);
            }
          });
        });
      },

      _upload(s3) {
        const archivePath = this.readConfig('archivePath');
        const archiveName = this._buildArchiveName();
        const prefix = this.readConfig('prefix');
        const key = prefix ? [prefix, archiveName].join('/') : archiveName;
        const fileName = path.join(archivePath, archiveName);
        const file = fs.createReadStream(fileName);
        const bucket = this.readConfig('bucket');
        const params = {
          Bucket: bucket,
          Key: key,
          Body: file
        };

        this.log(`preparing to upload to S3 bucket '${bucket}'`, {
          verbose: true
        });

        return s3.putObject(params).promise();
      },

      _uploadDeployInfo(s3 /*, key*/) {
        const deployInfo = this.readConfig('deployInfo');
        const bucket = this.readConfig('bucket');
        const prefix = this.readConfig('prefix');
        const body = this._createDeployInfo();
        const key = prefix ? [prefix, deployInfo].join('/') : deployInfo;
        const params = {
          Bucket: bucket,
          Key: key,
          Body: body
        };

        return s3.putObject(params).promise();
      },

      _createDeployInfo() {
        const bucket = this.readConfig('bucket');
        const prefix = this.readConfig('prefix');
        const archiveName = this._buildArchiveName();
        const key = prefix ? [prefix, archiveName].join('/') : archiveName;

        return `{"bucket":"${bucket}","key":"${key}"}`;
      },

      _pack() {
        return new RSVP.Promise((resolve, reject) => {
          const distDir = this.readConfig('distDir');
          const archivePath = this.readConfig('archivePath');
          const archiveType = this.readConfig('archiveType');
          const deployArchive = this.readConfig('deployArchive');

          fs.mkdirsSync(archivePath);

          const archiveName = this._buildArchiveName();
          const fileName = path.join(archivePath, archiveName);

          this.log(`saving deploy archive to ${fileName}`, {
            verbose: true
          });

          const output = fs.createWriteStream(fileName);
          const archive = archiver(archiveType, { zlib: { level: 9 } });

          archive.pipe(output);
          archive.directory(distDir, deployArchive).finalize();

          output.on('close', resolve);
          archive.on('error', (err) => reject(err));
        });
      },

      _buildArchiveName() {
        const deployArchive = this.readConfig('deployArchive');
        const revisionKey = this.readConfig('revisionKey');
        const archiveType = this.readConfig('archiveType');
        return `${deployArchive}-${revisionKey}.${archiveType}`;
      },

      _errorMessage(error) {
        this.log(error, { color: 'red' });
        if (error) {
          this.log(error.stack, { color: 'red' });
        }
        return RSVP.Promise.reject(error);
      }
    });

    return new DeployPlugin();
  }
};
<commit_msg>set activatedRevisionKey<commit_after>/* eslint-env node */
'use strict';

const BasePlugin = require('ember-cli-deploy-plugin');
const RSVP = require('rsvp');
const fs = require('fs-extra');
const path = require('path');
const archiver = require('archiver');
const AWS = require('aws-sdk');

const DEFAULT_DEPLOY_INFO = 'fastboot-deploy-info.json';
const DEFAULT_DEPLOY_ARCHIVE = 'dist';

module.exports = {
  name: 'ember-cli-deploy-fastboot-s3',

  createDeployPlugin(options) {
    const name = options.name;
    const DeployPlugin = BasePlugin.extend({
      name,
      defaultConfig: {
        archivePath: path.join('tmp', DEFAULT_DEPLOY_ARCHIVE),
        archiveType: 'zip',
        deployInfo: DEFAULT_DEPLOY_INFO,
        deployArchive: DEFAULT_DEPLOY_ARCHIVE,
        distDir: (context) => context.distDir,
        revisionKey: (context) => (
          context.commandOptions.revision ||
          (context.revisionData && context.revisionData.revisionKey)
        ),
        s3Client: (context) => context.s3Client,
      },

      requiredConfig: ['bucket'],

      configure(/*context*/) {
        // Ensure default config is applied
        this._super.configure.apply(this, arguments);

        // If a custom S3 client is configured then the rest of the
        // configuration is redundant.
        if (this.readConfig('s3Client')) {
          return;
        }

        // An endpoints makes the region config redundant, however
        // at least one of them must be present.
        if (!this.readConfig('region') && !this.readConfig('endpoint')) {
          const message = `You must configure either an 'endpoint' or a 'region' to use the AWS.S3 client.`;

          this.log(message, { color: 'red' });
          throw new Error(message);
        }
      },

      setup(/*context*/) {
        this.s3 =
          this.readConfig('s3Client') ||
          new AWS.S3({
            region: this.readConfig('region'),
            accessKeyId: this.readConfig('accessKeyId'),
            secretAccessKey: this.readConfig('secretAccessKey'),
            endpoint: this.readConfig('endpoint')
          });
      },

      didPrepare(/*context*/) {
        return this._pack()
          .then(() => {
            const archiveName = this._buildArchiveName();
            this.log(`✔  ${archiveName}`, { verbose: true });
          });
      },

      upload(/*context*/) {
        const prefix = this.readConfig('prefix');
        const archiveName = this._buildArchiveName();
        this.key = prefix ? [prefix, archiveName].join('/') : archiveName;

        return this._upload(this.s3)
          .then(() => {
            this.log(`✔  ${this.key}`, { verbose: true });
            return RSVP.Promise.resolve();
          })
          .catch(this._errorMessage.bind(this));
      },

      activate(context) {
        const revisionKey = this.readConfig('revisionKey');

        this.log(`preparing to activate ${revisionKey}`, {
          verbose: true
        });

        let _this = this;
        return this.fetchRevisions(context).then(function(revisions) {
           let found = revisions.revisions.map(function(element) { return element.revision; }).indexOf(revisionKey);
           if (found >= 0) {

             return _this._uploadDeployInfo(_this.s3)
               .then(() => {
                if(!context.revisionData) {
                  context.revisionData = {};
                }
                context.revisionData.activatedRevisionKey = revisionKey;
                 _this.log(`✔  activated revison ${revisionKey}`, {
                   verbose: true
                 });
               })
               .catch(_this._errorMessage.bind(_this));

           } else {
             return RSVP.reject("REVISION NOT FOUND!"); // see how we should handle a pipeline failure
           }
         });
      },

      didDeploy(context) {
        const revisionKey = context.revisionData && context.revisionData.revisionKey;
        const activatedRevisionKey = context.revisionData && context.revisionData.activatedRevisionKey;
        if (revisionKey && !activatedRevisionKey) {
          this.log("Deployed but did not activate revision " + revisionKey + ". "
               + "To activate, run: "
               + "ember deploy:activate " + context.deployTarget + " --revision=" + revisionKey + "\n"
             );
        }
      },

      fetchRevisions(context) {
        return this._list(context)
          .then(function(revisions) {
            return {
              revisions: revisions
            };
          });
      },

      _list(/* context */) {
        const bucket = this.readConfig('bucket');
        const deployArchive = this.readConfig('deployArchive');
        const archiveExt = `.${this.readConfig('archiveType')}`;
        const deployInfo = this.readConfig('deployInfo');
        const prefix = this.readConfig('prefix');
        const archivePath = prefix ? [prefix, deployArchive].join('/') : deployArchive;
        const indexKey = prefix ? [prefix, deployInfo].join('/') : deployInfo;

        let revisionPrefix = `${archivePath}-`;

        return RSVP.hash({
          revisions: this.listObjects(this.s3, { Bucket: bucket, Prefix: revisionPrefix }),
          current: this.getObject(this.s3, { Bucket: bucket, Key: indexKey }),
        })
        .then(function(data) {
          let activeRevision = '';
          if (data.current) {
            let objectData = data.current.Body.toString('utf-8');
            if (objectData[0] === '{') {
              let obj = JSON.parse(objectData);
              if (obj.key) {
                activeRevision = obj.key.substring(revisionPrefix.length, obj.key.lastIndexOf('.'));
              }
            }
          }

          let results = data.revisions.Contents.sort(function(a, b) {
            return new Date(b.LastModified) - new Date(a.LastModified);
          }).map(function(d) {
            let revision = '';
            /* Check that this is the type of configured archive. */
            if (d.Key.lastIndexOf(archiveExt) !== -1) {
              revision = d.Key.substring(revisionPrefix.length, d.Key.lastIndexOf('.'));
            }
            let active = data.current && revision === activeRevision;
            return { revision: revision, timestamp: d.LastModified, active: active, deployer: 'fastboot-s3' };
          }).filter(function(d) {
            /* Filter out results where revision is empty. */
            return d.revision !== '';
          });

          return results;
        }).catch(this._errorMessage.bind(this));
      },

      listObjects(s3, params) {
        return new RSVP.Promise(function(resolve, reject) {
          s3.listObjects(params, function(err, data) {
            if (err) {
              return reject(err);
            }
            return resolve(data);
          });
        });
      },

      getObject(s3, params) {
        return new RSVP.Promise(function(resolve, reject) {
          s3.getObject(params, function(err, data) {
            if (err && (err.code === 'NotFound' || err.code === 'NoSuchKey')) {
              return resolve();
            }
            else if (err) {
              return reject(err);
            }
            else {
              return resolve(data);
            }
          });
        });
      },

      _upload(s3) {
        const archivePath = this.readConfig('archivePath');
        const archiveName = this._buildArchiveName();
        const prefix = this.readConfig('prefix');
        const key = prefix ? [prefix, archiveName].join('/') : archiveName;
        const fileName = path.join(archivePath, archiveName);
        const file = fs.createReadStream(fileName);
        const bucket = this.readConfig('bucket');
        const params = {
          Bucket: bucket,
          Key: key,
          Body: file
        };

        this.log(`preparing to upload to S3 bucket '${bucket}'`, {
          verbose: true
        });

        return s3.putObject(params).promise();
      },

      _uploadDeployInfo(s3 /*, key*/) {
        const deployInfo = this.readConfig('deployInfo');
        const bucket = this.readConfig('bucket');
        const prefix = this.readConfig('prefix');
        const body = this._createDeployInfo();
        const key = prefix ? [prefix, deployInfo].join('/') : deployInfo;
        const params = {
          Bucket: bucket,
          Key: key,
          Body: body
        };

        return s3.putObject(params).promise();
      },

      _createDeployInfo() {
        const bucket = this.readConfig('bucket');
        const prefix = this.readConfig('prefix');
        const archiveName = this._buildArchiveName();
        const key = prefix ? [prefix, archiveName].join('/') : archiveName;

        return `{"bucket":"${bucket}","key":"${key}"}`;
      },

      _pack() {
        return new RSVP.Promise((resolve, reject) => {
          const distDir = this.readConfig('distDir');
          const archivePath = this.readConfig('archivePath');
          const archiveType = this.readConfig('archiveType');
          const deployArchive = this.readConfig('deployArchive');

          fs.mkdirsSync(archivePath);

          const archiveName = this._buildArchiveName();
          const fileName = path.join(archivePath, archiveName);

          this.log(`saving deploy archive to ${fileName}`, {
            verbose: true
          });

          const output = fs.createWriteStream(fileName);
          const archive = archiver(archiveType, { zlib: { level: 9 } });

          archive.pipe(output);
          archive.directory(distDir, deployArchive).finalize();

          output.on('close', resolve);
          archive.on('error', (err) => reject(err));
        });
      },

      _buildArchiveName() {
        const deployArchive = this.readConfig('deployArchive');
        const revisionKey = this.readConfig('revisionKey');
        const archiveType = this.readConfig('archiveType');
        return `${deployArchive}-${revisionKey}.${archiveType}`;
      },

      _errorMessage(error) {
        this.log(error, { color: 'red' });
        if (error) {
          this.log(error.stack, { color: 'red' });
        }
        return RSVP.Promise.reject(error);
      }
    });

    return new DeployPlugin();
  }
};
<|endoftext|>


<commit_before>import React from 'react';
import TimerMixin from 'react-timer-mixin';
import {
  ListView,
  LayoutAnimation,
  View,
  Animated,
  Dimensions,
  PanResponder,
  TouchableWithoutFeedback
} from 'react-native';

let HEIGHT = Dimensions.get('window').height;
var Row = React.createClass({
  shouldComponentUpdate: function(props) {
    if (props.hovering !== this.props.hovering) return true;
    if (props.active !== this.props.active) return true;
    if (props.rowData.data !== this.props.rowData.data) return true;
    return false;
  },
  handleLongPress: function(e) {
    this.refs.view.measure((frameX, frameY, frameWidth, frameHeight, pageX, pageY) => {
      let layout = {frameX, frameY, frameWidth, frameHeight, pageX, pageY};
       this.props.onRowActive({
        layout: layout,
        touch: e.nativeEvent,
        rowData: this.props.rowData
      });
    });
  },
  measure: function() {
    return this.refs.view.measure.apply(this, Array.from(arguments));
  },
  render: function() {
    let layout = this.props.list.layoutMap[this.props.rowData.index];
    let activeData = this.props.list.state.active;

    let activeIndex = activeData ? Number(activeData.rowData.index) : -5;
    let shouldDisplayHovering = activeIndex !== this.props.rowData.index;
    let Row = React.cloneElement(this.props.renderRow(this.props.rowData.data, this.props.rowData.section, this.props.rowData.index, null, this.props.active), {onLongPress: this.handleLongPress, onPressOut: this.props.list.cancel});
    return <View onLayout={this.props.onRowLayout} style={this.props.active && this.props.list.state.hovering ? {height: 0, opacity: 0} : null} ref="view">
          {this.props.hovering && shouldDisplayHovering ? this.props.activeDivider : null}
          {this.props.active && this.props.list.state.hovering && this.props._legacySupport ? null : Row}
        </View>
  }
});

var SortRow = React.createClass({
  getInitialState: function() {
    let layout = this.props.list.state.active.layout;
    let wrapperLayout = this.props.list.wrapperLayout;

    return {
      style: {
        position: 'absolute',
        left: 0,
        right: 0,
        opacity: .2,
        height: layout.frameHeight,
        overflow: 'hidden',
        backgroundColor: 'transparent',
        marginTop: layout.pageY - wrapperLayout.pageY //Account for top bar spacing
      }
    }
  },
  render: function() {
     let handlers = this.props.panResponder.panHandlers;
    return <Animated.View ref="view" style={[this.state.style, this.props.sortRowStyle, this.props.list.state.pan.getLayout()]}>
      <View style={{opacity: .85, flex: 1}}>
        {this.props.renderRow(this.props.rowData.data, this.props.rowData.section, this.props.rowData.index, true)}
      </View>
      </Animated.View>
  }
});

var SortableListView = React.createClass({
  mixins: [TimerMixin],
  getInitialState:function() {

    let currentPanValue = {x: 0, y: 0};

    this.state = {
      ds: new ListView.DataSource({rowHasChanged: (r1, r2) => {
      //  if (this.props.rowHasChanged) return this.props.rowHasChanged(r1, r2);
        return false;
      }}),
      active: false,
      hovering: false,
      pan: new Animated.ValueXY(currentPanValue)
    };
    this.listener = this.state.pan.addListener(e => this.panY = e.y);
    let onPanResponderMoveCb = Animated.event([null, {
           dx: this.state.pan.x, // x,y are Animated.Value
           dy: this.state.pan.y,
      }]);

    this.state.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e) => true,
      onMoveShouldSetPanResponderCapture: (e, a) => {
        //Only capture when moving vertically, this helps for child swiper rows.
        let vy = Math.abs(a.vy);
        let vx = Math.abs(a.vx);

        return (vy) > vx  && this.state.active;
      },
      onPanResponderMove: (evt, gestureState) => {
        gestureState.dx = 0;
        this.moveY = gestureState.moveY;
        onPanResponderMoveCb(evt, gestureState);
       },

       onPanResponderGrant: (e, gestureState) => {
          this.moved = true;
          this.props.onMoveStart &&  this.props.onMoveStart();
          this.state.pan.setOffset(currentPanValue);
          this.state.pan.setValue(currentPanValue);
      },
      onPanResponderRelease: (e) => {
        this.moved = false;
        this.props.onMoveEnd && this.props.onMoveEnd();
        if (!this.state.active) {
          if (this.state.hovering) this.setState({hovering: false});
          return;
        }
        let itemHeight = this.state.active.layout.frameHeight;
        let fromIndex = this.order.indexOf(this.state.active.rowData.index);
        let toIndex = this.state.hovering === false ?  fromIndex : Number(this.state.hovering);
        let up = toIndex > fromIndex;
        if (up) {
          toIndex--;
        }
        if (toIndex === fromIndex) return this.setState({active: false, hovering: false});
        let args = {
          row: this.state.active.rowData,
          from: fromIndex,
          to: toIndex
        };

        this.props.onRowMoved && this.props.onRowMoved(args);
        if (this.props._legacySupport) { //rely on parent data changes to set state changes
          //LayoutAnimation.easeInEaseOut()
          this.state.active = false;
          this.state.hovering = false;
        } else {
          this.setState({
            active: false,
            hovering: false
          });
        }

        let MAX_HEIGHT = Math.max(0, this.scrollContainerHeight - HEIGHT);
        if (this.scrollValue > MAX_HEIGHT) {
          this.scrollResponder.scrollTo({y: MAX_HEIGHT});
        }

        this.state.active = false;
        this.state.hovering = false;
      }
     });

    return this.state;
  },
  cancel: function() {
    if (!this.moved) {
      this.setState({
        active: false,
        hovering: false
      });
    }
  },
  componentDidMount: function() {
    setTimeout(()=>{
      this.scrollResponder = this.refs.list.getScrollResponder();
      this.refs.wrapper.measure((frameX, frameY, frameWidth, frameHeight, pageX, pageY) => {

        let layout = {frameX, frameY, frameWidth, frameHeight, pageX, pageY};
        this.wrapperLayout = layout;
      });
    }, 1);

  },
  scrollValue: 0,
  scrollContainerHeight: HEIGHT * 1.2, //Gets calculated on scroll, but if you havent scrolled needs an initial value
  scrollAnimation: function() {
    if (this.isMounted() && this.state.active) {
      if (this.moveY == undefined) return this.requestAnimationFrame(this.scrollAnimation);

      let SCROLL_OFFSET = this.wrapperLayout.pageY;
      let moveY = this.moveY - SCROLL_OFFSET;
      let SCROLL_LOWER_BOUND = 80 + SCROLL_OFFSET;
      let SCROLL_HIGHER_BOUND = this.listLayout.height - SCROLL_LOWER_BOUND;

      let MAX_SCROLL_VALUE = this.scrollContainerHeight - HEIGHT + (this.state.active.layout.frameHeight * 2);
      let currentScrollValue = this.scrollValue;
      let newScrollValue = null;
      let SCROLL_MAX_CHANGE = 20;

      if (moveY < SCROLL_LOWER_BOUND && currentScrollValue > 0) {
        let PERCENTAGE_CHANGE = 1 - (moveY / SCROLL_LOWER_BOUND);
          newScrollValue = currentScrollValue - (PERCENTAGE_CHANGE * SCROLL_MAX_CHANGE);
          if (newScrollValue < 0) newScrollValue = 0;

      }
      if (moveY > SCROLL_HIGHER_BOUND && currentScrollValue < MAX_SCROLL_VALUE) {
        let PERCENTAGE_CHANGE = 1 - ((this.listLayout.height - moveY) / SCROLL_LOWER_BOUND);
        newScrollValue = currentScrollValue + (PERCENTAGE_CHANGE * SCROLL_MAX_CHANGE);
        if (newScrollValue > MAX_SCROLL_VALUE) newScrollValue = MAX_SCROLL_VALUE;
      }
      if (newScrollValue !== null) {
        this.scrollValue = newScrollValue;
         //this.scrollResponder.scrollWithoutAnimationTo(this.scrollValue, 0);
         this.scrollResponder.scrollTo({y: this.scrollValue, x: 0, animated: false});
      }
      this.checkTargetElement();
      this.requestAnimationFrame(this.scrollAnimation);
    }
  },
  calculateDestinationY: function(index) {
    let i = 0;
    let x = 0;
    let row;
    let order = this.order;

    for (x = 0; x < order.length; x++) {
      let key = order[x];
      row = this.layoutMap[key];
      if (x == index) return i;
      i += row.height;
    }
    return 0;
  },
  checkTargetElement() {
    let scrollValue = this.scrollValue;
    let moveY = this.moveY;
    let targetPixel = scrollValue + moveY - this.wrapperLayout.pageY;
    let i = 0;
    let x = 0;
    let row;
    let order = this.order;
    let isLast = false;
    while (i < targetPixel) {
      let key = order[x];
      row = this.layoutMap[key];
      if (!row) {
        isLast = true;
        break;
      }
      i += row.height;
      x++;
    }
    if (!isLast) x--;
    if (x != this.state.hovering) {
      LayoutAnimation.easeInEaseOut();
      this._previouslyHovering = this.state.hovering;
      this.__activeY = this.panY;
      this.setState({
        hovering: String(x)
      })
    }

  },
  layoutMap: {},
  _rowRefs: {},
  handleRowActive: function(row) {
    this.state.pan.setValue({x: 0, y: 0});
    LayoutAnimation.easeInEaseOut();
    this.setState({
      active: row
    },  this.scrollAnimation);

  },
  renderActiveDivider: function() {
    if (this.props.activeDivider) this.props.activeDivider();
    return <View style={{height: this.state.active ? this.state.active.layout.frameHeight : null}} />
  },
  renderRow: function(data, section, index, highlightfn, active) {
    let Component = active ? SortRow : Row;
    let isActiveRow = (!active && this.state.active && this.state.active.rowData.index === index);
    if (!active && isActiveRow) {
      active = {active: true};
    }
    let hoveringIndex = this.order[this.state.hovering];
    return <Component
      {...this.props}
      activeDivider={this.renderActiveDivider() }
      key={index}
      active={active}
      list={this}
      ref={view => { this._rowRefs[active ? 'ghost' : index] = view; }}
      hovering={hoveringIndex == index}
      panResponder={this.state.panResponder}
      rowData={{data, section, index}}
      onRowActive={this.handleRowActive}
      onRowLayout={layout => this.layoutMap[index] = layout.nativeEvent.layout}
      />
  },
  renderActive: function() {
    if (!this.state.active) return;
    let index = this.state.active.rowData.index;
    return this.renderRow(this.props.data[index], 's1', index, () => {}, {active: true, thumb: true});
  },
  componentWillMount: function() {
    this.setOrder(this.props);
  },
  componentWillReceiveProps: function(props) {
    this.setOrder(props);
  },
  setOrder: function(props) {
    this.order = props.order || Object.keys(props.data) || [];
  },
  getScrollResponder: function() {
    return this.scrollResponder;
  },
  render: function() {
    let dataSource = this.state.ds.cloneWithRows(this.props.data, this.props.order);
    return <View ref="wrapper" style={{flex: 1}} onLayout={()=>{}}>
      <ListView
        enableEmptySections={true}
        {...this.props}
        {...this.state.panResponder.panHandlers}
        ref="list"
        dataSource={dataSource}
        onScroll={e => {
          this.scrollValue = e.nativeEvent.contentOffset.y;
          if (!this._scrolling) {
            // Only cache the scroll container height at the beginning of the scroll.
            this._scrolling = true;
            this.scrollContainerHeight = e.nativeEvent.contentSize.height;
          }

          if (this.props.onScroll) this.props.onScroll(e);
        }}
        onScrollAnimationEnd={() => this._scrolling = false}
        onLayout={(e) => this.listLayout = e.nativeEvent.layout}
        scrollEnabled={!this.state.active}
        renderRow={this.renderRow}
      />
      {this.renderActive()}
    </View>
  }
});

module.exports = SortableListView;
<commit_msg>Improve listview with margin + clean code<commit_after>import React from 'react';
import TimerMixin from 'react-timer-mixin';
import {
  ListView,
  LayoutAnimation,
  View,
  Animated,
  Dimensions,
  PanResponder,
  TouchableWithoutFeedback
} from 'react-native';

let HEIGHT = Dimensions.get('window').height;
var Row = React.createClass({
  shouldComponentUpdate: function(props) {
    if (props.hovering !== this.props.hovering) return true;
    if (props.active !== this.props.active) return true;
    if (props.rowData.data !== this.props.rowData.data) return true;
    return false;
  },
  handleLongPress: function(e) {
    this.refs.view.measure((frameX, frameY, frameWidth, frameHeight, pageX, pageY) => {
      let layout = {frameX, frameY, frameWidth, frameHeight, pageX, pageY};
       this.props.onRowActive({
        layout: layout,
        touch: e.nativeEvent,
        rowData: this.props.rowData
      });
    });
  },
  measure: function() {
    return this.refs.view.measure.apply(this, Array.from(arguments));
  },
  render: function() {
    let layout = this.props.list.layoutMap[this.props.rowData.index];
    let activeData = this.props.list.state.active;

    let activeIndex = activeData ? Number(activeData.rowData.index) : -5;
    let shouldDisplayHovering = activeIndex !== this.props.rowData.index;
    let Row = React.cloneElement(this.props.renderRow(this.props.rowData.data, this.props.rowData.section, this.props.rowData.index, null, this.props.active), {onLongPress: this.handleLongPress, onPressOut: this.props.list.cancel});
    return <View onLayout={this.props.onRowLayout} style={this.props.active && this.props.list.state.hovering ? {height: 0, opacity: 0} : null} ref="view">
          {this.props.hovering && shouldDisplayHovering ? this.props.activeDivider : null}
          {this.props.active && this.props.list.state.hovering && this.props._legacySupport ? null : Row}
        </View>
  }
});

var SortRow = React.createClass({
  getInitialState: function() {
    let layout = this.props.list.state.active.layout;
    let wrapperLayout = this.props.list.wrapperLayout;

    return {
      style: {
        position: 'absolute',
        left: 0,
        right: 0,
        opacity: .2,
        height: layout.frameHeight,
        overflow: 'hidden',
        backgroundColor: 'transparent',
        marginTop: layout.pageY - wrapperLayout.pageY //Account for top bar spacing
      }
    }
  },
  render: function() {
    let handlers = this.props.panResponder.panHandlers;
    return <Animated.View ref="view" style={[this.state.style, this.props.sortRowStyle, this.props.list.state.pan.getLayout()]}>
      <View style={{opacity: .85, flex: 1}}>
        {this.props.renderRow(this.props.rowData.data, this.props.rowData.section, this.props.rowData.index, null, true)}
      </View>
      </Animated.View>
  }
});

var SortableListView = React.createClass({
  mixins: [TimerMixin],
  getInitialState:function() {

    let currentPanValue = {x: 0, y: 0};

    this.state = {
      ds: new ListView.DataSource({rowHasChanged: (r1, r2) => {
      //  if (this.props.rowHasChanged) return this.props.rowHasChanged(r1, r2);
        return false;
      }}),
      active: false,
      hovering: false,
      pan: new Animated.ValueXY(currentPanValue)
    };
    this.listener = this.state.pan.addListener(e => this.panY = e.y);
    let onPanResponderMoveCb = Animated.event([null, {
           dx: this.state.pan.x, // x,y are Animated.Value
           dy: this.state.pan.y,
      }]);

    this.state.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e) => true,
      onMoveShouldSetPanResponderCapture: (e, a) => {
        //Only capture when moving vertically, this helps for child swiper rows.
        let vy = Math.abs(a.vy);
        let vx = Math.abs(a.vx);

        return (vy) > vx  && this.state.active;
      },
      onPanResponderMove: (evt, gestureState) => {
        gestureState.dx = 0;
        this.moveY = gestureState.moveY;
        onPanResponderMoveCb(evt, gestureState);
       },

       onPanResponderGrant: (e, gestureState) => {
          this.moved = true;
          this.props.onMoveStart &&  this.props.onMoveStart();
          this.state.pan.setOffset(currentPanValue);
          this.state.pan.setValue(currentPanValue);
      },
      onPanResponderRelease: (e) => {
        this.moved = false;
        this.props.onMoveEnd && this.props.onMoveEnd();
        if (!this.state.active) {
          if (this.state.hovering) this.setState({hovering: false});
          return;
        }
        let itemHeight = this.state.active.layout.frameHeight;
        let fromIndex = this.order.indexOf(this.state.active.rowData.index);
        let toIndex = this.state.hovering === false ?  fromIndex : Number(this.state.hovering);
        let up = toIndex > fromIndex;
        if (up) {
          toIndex--;
        }
        if (toIndex === fromIndex) return this.setState({active: false, hovering: false});
        let args = {
          row: this.state.active.rowData,
          from: fromIndex,
          to: toIndex
        };

        this.props.onRowMoved && this.props.onRowMoved(args);
        if (this.props._legacySupport) { //rely on parent data changes to set state changes
          //LayoutAnimation.easeInEaseOut()
          this.state.active = false;
          this.state.hovering = false;
        } else {
          this.setState({
            active: false,
            hovering: false
          });
        }

        let MAX_HEIGHT = Math.max(0, this.scrollContainerHeight - this.listLayout.height);
        if (this.scrollValue > MAX_HEIGHT) {
          this.scrollResponder.scrollTo({y: MAX_HEIGHT});
        }

        this.state.active = false;
        this.state.hovering = false;
      }
     });

    return this.state;
  },
  cancel: function() {
    if (!this.moved) {
      this.setState({
        active: false,
        hovering: false
      });
    }
  },
  componentDidMount: function() {
    setTimeout(()=>{
      this.scrollResponder = this.refs.list.getScrollResponder();
      this.refs.wrapper.measure((frameX, frameY, frameWidth, frameHeight, pageX, pageY) => {

        let layout = {frameX, frameY, frameWidth, frameHeight, pageX, pageY};
        this.wrapperLayout = layout;
      });
    }, 1);

  },
  scrollValue: 0,
  scrollContainerHeight: HEIGHT * 1.2, //Gets calculated on scroll, but if you havent scrolled needs an initial value
  scrollAnimation: function() {
    if (this.isMounted() && this.state.active) {
      if (this.moveY == undefined) return this.requestAnimationFrame(this.scrollAnimation);

      let SCROLL_OFFSET = this.wrapperLayout.pageY;
      let moveY = this.moveY - SCROLL_OFFSET;
      let SCROLL_LOWER_BOUND = 80;
      let SCROLL_HIGHER_BOUND = this.listLayout.height - SCROLL_LOWER_BOUND;

      let MAX_SCROLL_VALUE = this.scrollContainerHeight - this.listLayout.height + (this.state.active.layout.frameHeight * 2);
      let currentScrollValue = this.scrollValue;
      let newScrollValue = null;
      let SCROLL_MAX_CHANGE = 20;

      if (moveY < SCROLL_LOWER_BOUND && currentScrollValue > 0) {
        let PERCENTAGE_CHANGE = 1 - (moveY / SCROLL_LOWER_BOUND);
        newScrollValue = currentScrollValue - (PERCENTAGE_CHANGE * SCROLL_MAX_CHANGE);
        if (newScrollValue < 0) newScrollValue = 0;
      }
      if (moveY > SCROLL_HIGHER_BOUND && currentScrollValue < MAX_SCROLL_VALUE) {
        let PERCENTAGE_CHANGE = 1 - ((this.listLayout.height - moveY) / SCROLL_LOWER_BOUND);
        newScrollValue = currentScrollValue + (PERCENTAGE_CHANGE * SCROLL_MAX_CHANGE);
        if (newScrollValue > MAX_SCROLL_VALUE) newScrollValue = MAX_SCROLL_VALUE;
      }
      if (newScrollValue !== null) {
        this.scrollValue = newScrollValue;
         //this.scrollResponder.scrollWithoutAnimationTo(this.scrollValue, 0);
         this.scrollResponder.scrollTo({y: this.scrollValue, x: 0, animated: false});
      }
      this.checkTargetElement();
      this.requestAnimationFrame(this.scrollAnimation);
    }
  },
  checkTargetElement() {
    let scrollValue = this.scrollValue;
    let moveY = this.moveY;
    let targetPixel = scrollValue + moveY - this.wrapperLayout.pageY;
    let i = 0;
    let x = 0;
    let row;
    let order = this.order;
    let isLast = false;
    while (i < targetPixel) {
      let key = order[x];
      row = this.layoutMap[key];
      if (!row) {
        isLast = true;
        break;
      }
      i += row.height;
      x++;
    }
    if (!isLast) x--;
    if (x != this.state.hovering) {
      LayoutAnimation.easeInEaseOut();
      this._previouslyHovering = this.state.hovering;
      this.__activeY = this.panY;
      this.setState({
        hovering: String(x)
      })
    }

  },
  layoutMap: {},
  _rowRefs: {},
  handleRowActive: function(row) {
    this.state.pan.setValue({x: 0, y: 0});
    LayoutAnimation.easeInEaseOut();
    this.setState({
      active: row
    },  this.scrollAnimation);

  },
  renderActiveDivider: function() {
    let height = this.state.active ? this.state.active.layout.frameHeight : null
    if (this.props.renderActiveDivider) return this.props.renderActiveDivider(height);
    return <View style={{height: height}} />
  },
  renderRow: function(data, section, index, highlightfn, active) {
    let Component = active ? SortRow : Row;
    let isActiveRow = (!active && this.state.active && this.state.active.rowData.index === index);
    if (!active && isActiveRow) {
      active = {active: true};
    }
    let hoveringIndex = this.order[this.state.hovering];
    return <Component
      {...this.props}
      activeDivider={this.renderActiveDivider()}
      key={index}
      active={active}
      list={this}
      ref={view => { this._rowRefs[active ? 'ghost' : index] = view; }}
      hovering={hoveringIndex == index}
      panResponder={this.state.panResponder}
      rowData={{data, section, index}}
      onRowActive={this.handleRowActive}
      onRowLayout={layout => this.layoutMap[index] = layout.nativeEvent.layout}
      />
  },
  renderActive: function() {
    if (!this.state.active) return;
    let index = this.state.active.rowData.index;
    return this.renderRow(this.props.data[index], 's1', index, () => {}, {active: true, thumb: true});
  },
  componentWillMount: function() {
    this.setOrder(this.props);
  },
  componentWillReceiveProps: function(props) {
    this.setOrder(props);
  },
  setOrder: function(props) {
    this.order = props.order || Object.keys(props.data) || [];
  },
  getScrollResponder: function() {
    return this.scrollResponder;
  },
  render: function() {
    let dataSource = this.state.ds.cloneWithRows(this.props.data, this.props.order);
    return <View ref="wrapper" style={{flex: 1}} onLayout={()=>{}}>
      <ListView
        enableEmptySections={true}
        {...this.props}
        {...this.state.panResponder.panHandlers}
        ref="list"
        dataSource={dataSource}
        onScroll={e => {
          this.scrollValue = e.nativeEvent.contentOffset.y;
          if (!this._scrolling) {
            // Only cache the scroll container height at the beginning of the scroll.
            this._scrolling = true;
            this.scrollContainerHeight = e.nativeEvent.contentSize.height;
          }

          if (this.props.onScroll) this.props.onScroll(e);
        }}
        onScrollAnimationEnd={() => this._scrolling = false}
        onLayout={(e) => this.listLayout = e.nativeEvent.layout}
        scrollEnabled={!this.state.active && this.props.scrollEnabled}
        renderRow={this.renderRow}
      />
      {this.renderActive()}
    </View>
  }
});

module.exports = SortableListView;
<|endoftext|>


<commit_before>const socketJwt = require('socketio-jwt');
const Server = require('socket.io');
const config = require('../config/settings');
const Message = require('../models/message');
const notificationTypes = require('../models/constants').notifications;
const initMusicServer = require('./musicServer');
const rooms = {};
let rootSocket = {};

module.exports = function initializeSocketServer(httpServer) {
  rootSocket = new Server(httpServer);

  rootSocket.use(socketJwt.authorize({
    secret: config.secret,
    handshake: true
  }));

  rootSocket.on('connection', (clientSocket) => {
    const user = clientSocket.decoded_token.user;

    clientSocket.on('join', roomId => {
      clientSocket.join(roomId, () => {
        // We can't use the rooms property here to notify all the rooms of the disconnect,
        // since the property has already been cleared, so we define a property ___radRooms on the socket.
        clientSocket.___radRooms = clientSocket.rooms;
        clientSocket.___radUser = user;

        sendMessage(roomId, {
          author: {
            username: 'System'
          },
          message: `${user.username} connected`
        });
      });

      // create a 'music server'
      if (!rooms[roomId]) {
        rooms[roomId] = initMusicServer(roomId, rootSocket.to(roomId),
          user._id);
      } else {
        rooms[roomId].join(user._id);
      }
    });

    clientSocket.on('message', (data) => {
      const { roomId } = data;

      if (!isInRoom(clientSocket, roomId)) {
        return;
      }

      onMessage(Object.assign(data, {
        shouldNotify: true
      }));
    });

    clientSocket.on('disconnect', () => {
      let keys;

      if (!clientSocket || !clientSocket.__radRooms) {
        return;
      }
      // We loop over the ___radRooms property to notify the rooms the user was last in.
      try {
        keys = Object.keys(clientSocket.___radRooms).slice(1, clientSocket.___radRooms.length);
      } catch(error) {
        console.error(error);
        console.trace();
      }

      keys.forEach(key => {

        rooms[key].leave(user);

        sendMessage(key, {
          author: {
            username: 'System'
          },
          message: `${user.username} disconnected`
        });
      });
    });

    clientSocket.on('add', (data) => {
      const { roomId } = data;
      if (!isInRoom(clientSocket, roomId)) {
        return;
      }

      const enrichedData = Object.assign({}, {
        user
      }, data);

      rooms[roomId].add(enrichedData).then(() => {
        sendNotification(clientSocket, {
          type: notificationTypes.info,
          message: 'Successfully added'
        });
      }).catch((err) => {
        console.log(err);

        sendNotification(clientSocket, {
          type: notificationTypes.error,
          message: err || 'Failed to add video'
        });
      });

    });

    clientSocket.on('rtv', (data) => {
      if (!isInRoom(clientSocket, data.roomId)) {
        return;
      }

      const { roomId } = data;
      rooms[roomId].rtv(user._id).then(() => {

      }).catch((err) => {
        sendNotification(clientSocket, {
          type: notificationTypes.error,
          message: err
        });
      });
    });
  });

  rootSocket.on('unauthorized', function(err){
    console.log('There was an error with the authentication:', err.message);
  });

  return rootSocket;
};

const onMessage = (data) => {
  // Just send the message to all the clients in the room,
  // the user that sent it will receive it as well, but this is desirable.
  // If the user doesn't see their message pop up, it hasn't arrived.
  rootSocket.to(data.roomId).send(data);

  const message = new Message({
    author: data.author._id,
    message: data.message,
    room: data.roomId,
  });

  message.save().catch((err) => {
    console.log('ERR_SAVING_MSG: ', err);
  });
};

const isInRoom = (socket, roomId) => {
  if (!socket || !socket.rooms || !socket.rooms.hasOwnProperty(roomId)) {
    return false;
  }

  return true;
};

const sendMessage = (room, data) => {
  rootSocket.to(room).send(data);
};

const sendNotification = (clientSocket, data) => {
  clientSocket.emit('notification', data);
};<commit_msg>fix(server): correctly show notification when a user disconnects<commit_after>const socketJwt = require('socketio-jwt');
const Server = require('socket.io');
const config = require('../config/settings');
const Message = require('../models/message');
const notificationTypes = require('../models/constants').notifications;
const initMusicServer = require('./musicServer');
const rooms = {};
let rootSocket = {};

module.exports = function initializeSocketServer(httpServer) {
  rootSocket = new Server(httpServer);

  rootSocket.use(socketJwt.authorize({
    secret: config.secret,
    handshake: true
  }));

  rootSocket.on('connection', (clientSocket) => {
    const user = clientSocket.decoded_token.user;

    clientSocket.on('join', roomId => {
      clientSocket.join(roomId, () => {
        // We can't use the rooms property here to notify all the rooms of the disconnect,
        // since the property has already been cleared, so we define a property ___radRooms on the socket.
        clientSocket.___radRooms = clientSocket.rooms;
        clientSocket.___radUser = user;

        sendMessage(roomId, {
          author: {
            username: 'System'
          },
          message: `${user.username} connected`
        });
      });

      // create a 'music server'
      if (!rooms[roomId]) {
        rooms[roomId] = initMusicServer(roomId, rootSocket.to(roomId),
          user._id);
      } else {
        rooms[roomId].join(user._id);
      }
    });

    clientSocket.on('message', (data) => {
      const { roomId } = data;

      if (!isInRoom(clientSocket, roomId)) {
        return;
      }

      onMessage(Object.assign(data, {
        shouldNotify: true
      }));
    });

    clientSocket.on('disconnect', () => {
      let keys;

      if (!clientSocket || !clientSocket.___radRooms) {
        return;
      }
      // We loop over the ___radRooms property to notify the rooms the user was last in.
      try {
        keys = Object.keys(clientSocket.___radRooms).slice(1, clientSocket.___radRooms.length);
      } catch(error) {
        console.error(error);
        console.trace();
      }

      keys.forEach(key => {

        rooms[key].leave(user);

        sendMessage(key, {
          author: {
            username: 'System'
          },
          message: `${user.username} disconnected`
        });
      });
    });

    clientSocket.on('add', (data) => {
      const { roomId } = data;
      if (!isInRoom(clientSocket, roomId)) {
        return;
      }

      const enrichedData = Object.assign({}, {
        user
      }, data);

      rooms[roomId].add(enrichedData).then(() => {
        sendNotification(clientSocket, {
          type: notificationTypes.info,
          message: 'Successfully added'
        });
      }).catch((err) => {
        console.log(err);

        sendNotification(clientSocket, {
          type: notificationTypes.error,
          message: err || 'Failed to add video'
        });
      });

    });

    clientSocket.on('rtv', (data) => {
      if (!isInRoom(clientSocket, data.roomId)) {
        return;
      }

      const { roomId } = data;
      rooms[roomId].rtv(user._id).then(() => {

      }).catch((err) => {
        sendNotification(clientSocket, {
          type: notificationTypes.error,
          message: err
        });
      });
    });
  });

  rootSocket.on('unauthorized', function(err){
    console.log('There was an error with the authentication:', err.message);
  });

  return rootSocket;
};

const onMessage = (data) => {
  // Just send the message to all the clients in the room,
  // the user that sent it will receive it as well, but this is desirable.
  // If the user doesn't see their message pop up, it hasn't arrived.
  rootSocket.to(data.roomId).send(data);

  const message = new Message({
    author: data.author._id,
    message: data.message,
    room: data.roomId,
  });

  message.save().catch((err) => {
    console.log('ERR_SAVING_MSG: ', err);
  });
};

const isInRoom = (socket, roomId) => {
  if (!socket || !socket.rooms || !socket.rooms.hasOwnProperty(roomId)) {
    return false;
  }

  return true;
};

const sendMessage = (room, data) => {
  rootSocket.to(room).send(data);
};

const sendNotification = (clientSocket, data) => {
  clientSocket.emit('notification', data);
};<|endoftext|>


<commit_before>#!/usr/bin/env node

'use strict';

var argv = require('optimist').argv;
if (argv.h || argv.help) {
  console.log('\nusage:');
  console.log('-h, --help        : show help');
  console.log('-p, --port        : port (default 5080)');
  console.log('-P, --pouch-port : pouchdb-server port (default 16984)');
  console.log('-l, --log         : pouchdb-server log level (ev|short|tiny|combined|off)');
  console.log();
  return process.exit(0);
}

var port = argv.p || argv.port || 5080;
var pouchPort = argv.P || argv['pouch-port'] || 16984;

var SKIM_REMOTE = 'https://skimdb.npmjs.com/registry';
var SKIM_LOCAL = 'http://localhost:' + pouchPort + '/skimdb';
var FAT_LOCAL = 'http://localhost:' + pouchPort + '/fullfatdb';
var FAT_REMOTE = 'http://registry.npmjs.org';

var request = require('request');
var Promise = require('bluebird');
var fs = require('fs');
var Fullfat = require('npm-fullfat-registry');
var pouchdbServerLite = require('./pouchdb-server-lite');
var app = pouchdbServerLite.app;
var PouchDB = pouchdbServerLite.PouchDB;

var skimPouch = new PouchDB('skimdb');
var fatPouch = new PouchDB('fullfatdb');

// replicate from central skimdb
var upToDate;
var startingTimeout = 1000;
var backoff = 1.1;

var fullFat;
var sync;

function replicateForever() {
  var skimRemote = new PouchDB(SKIM_REMOTE);
  skimRemote.info().then(function (info) {
    sync = skimPouch.replicate.from(SKIM_REMOTE, {
      live: true,
      batch_size: 1000
    }).on('change', function (change) {
      var percent = (Math.round(change.last_seq / info.update_seq * 10000) / 100).toFixed(2);
      console.log('Replicating skimdb, last_seq is: ' + change.last_seq + ' (' + percent + '%)');
    }).on('uptodate', function () {
      console.log('local skimdb is up to date');
      skimPouch.put({_id: '_local/upToDate', upToDate: true});
      upToDate = true;
    }).on('error', function (err) {
      console.log('error during replication with skimdb');
      console.error(err);
      // just keep going
      setTimeout(replicateForever, Math.round(startingTimeout * backoff));
    });
  }).catch(function (err) {
    console.log('error doing info() on skimdb');
    console.error(err);
  });
}
replicateForever();

// start doing exciting shit
Promise.resolve().then(function () {
  return skimPouch.get('_local/upToDate').catch(function (err) {
    if (err.status !== 404) {
      throw err;
    }
    return {}; // default doc
  });
}).then(function (doc) {
  upToDate = doc.upToDate;
  if (upToDate) {
    console.log('local skimdb is up to date');
  }
}).then(function () {
  // trick Fullfat into never doing automatic syncing; we don't use it for that
  return Promise.promisify(fs.writeFile)('registry.seq', '999999999999');
}).then(function () {
  fullFat = new Fullfat({
    skim: upToDate ? SKIM_LOCAL : SKIM_REMOTE,
    fat: FAT_LOCAL,
    seq_file: 'registry.seq',
    missing_log: 'missing.log'
  });
  fullFat.on('error', function (err) {
    console.log("fullfat hit an error");
    console.error(err);
  });
}).then(function () {
  // start user-facing proxy server

  // only execute n fullFat.js operations in parallel
  var NUM_PARALLEL = 5;
  var queues = [];
  for (var i = 0; i < NUM_PARALLEL; i++) {
    queues.push(Promise.resolve());
  }

  function processWithFullFat(docId) {
    console.log('fetching ' + docId + ' with fullFat.js');
    // recover by fetching with fullFat
    // wait for changes to let us know it was fetched
    var queueIdx = docId.charCodeAt(0) % queues.length;
    queues[queueIdx] = queues[queueIdx].then(function () {
      console.log('docId is ' + docId + ', looking up in pouch');
      return fatPouch.get(docId).catch(function (err) {
        console.log('pouch responded');
        if (err.status !== 404) {
          throw err;
        }
        return new Promise(function (resolve, reject) {
          var changes = fatPouch.changes({
            since: 'latest',
            live: true
          }).on('change', function (change) {
              console.log('got change: ' + change.id);
              if (change.id === docId) {
                console.log('successfully wrote to local fullfatdb: ' + docId);
                changes.cancel();
                changes.removeAllListeners('change'); // TODO: shouldn't have to do this
                resolve();
              }
            }).on('error', reject);
          console.log('telling fat to fetch it');
          fullFat.getDoc({id: docId});
        });
      });
    });
    return queues[queueIdx];
  }

  var server = require('http').createServer(function (req, res) {
    Promise.resolve().then(function () {
      if (req.method.toLowerCase() === 'get' &&
          /^\/fullfatdb\/([^\/]+)/.test(req.url)) {
        // simple doc or attachment request (get)
        console.log('simple doc request');
        var url = require('url').parse(req.url);
        var docId = decodeURIComponent(url.pathname.split('/')[2]);
        // check if it exists first
        return processWithFullFat(docId);
      }
    }).then(function (doc) {
      console.log('doc exists locally, using local fat');
      request.get('http://127.0.0.1:' + pouchPort + req.url).pipe(res);
    }).catch(function (err) {
      console.log('error, need to use remote fat instead');
      console.error(err);
      var url = req.url.replace(/^\/fullfatdb/, '');
      request.get(FAT_REMOTE + url).pipe(res);
    });
  });
  server.listen(5080, function (err) {
    if (err) {
      console.error(err);
    } else {
      console.log('Proxy server started at http://127.0.0.1:' + port);
    }
  });
}).catch(function (err) {
  console.error(err);
  throw err;
});

process.on('SIGINT', function () {
  // close gracefully
  sync.cancel();
  fatPouch.close().then(function () {
    return skimPouch.close();
  }).then(function () {
    process.exit(0);
  });
});<commit_msg>add random seq<commit_after>#!/usr/bin/env node

'use strict';

var argv = require('optimist').argv;
if (argv.h || argv.help) {
  console.log('\nusage:');
  console.log('-h, --help        : show help');
  console.log('-p, --port        : port (default 5080)');
  console.log('-P, --pouch-port : pouchdb-server port (default 16984)');
  console.log('-l, --log         : pouchdb-server log level (ev|short|tiny|combined|off)');
  console.log();
  return process.exit(0);
}

var port = argv.p || argv.port || 5080;
var pouchPort = argv.P || argv['pouch-port'] || 16984;

var SKIM_REMOTE = 'https://skimdb.npmjs.com/registry';
var SKIM_LOCAL = 'http://localhost:' + pouchPort + '/skimdb';
var FAT_LOCAL = 'http://localhost:' + pouchPort + '/fullfatdb';
var FAT_REMOTE = 'http://registry.npmjs.org';

var request = require('request');
var Promise = require('bluebird');
var fs = require('fs');
var Fullfat = require('npm-fullfat-registry');
var pouchdbServerLite = require('./pouchdb-server-lite');
var app = pouchdbServerLite.app;
var PouchDB = pouchdbServerLite.PouchDB;

var skimPouch = new PouchDB('skimdb');
var fatPouch = new PouchDB('fullfatdb');

// replicate from central skimdb
var upToDate;
var startingTimeout = 1000;
var backoff = 1.1;

var fullFat;
var sync;

function replicateForever() {
  var skimRemote = new PouchDB(SKIM_REMOTE);
  skimRemote.info().then(function (info) {
    sync = skimPouch.replicate.from(SKIM_REMOTE, {
      live: true,
      batch_size: 1000
    }).on('change', function (change) {
      var percent = (Math.round(change.last_seq / info.update_seq * 10000) / 100).toFixed(2);
      console.log('Replicating skimdb, last_seq is: ' + change.last_seq + ' (' + percent + '%)');
    }).on('uptodate', function () {
      console.log('local skimdb is up to date');
      skimPouch.put({_id: '_local/upToDate', upToDate: true});
      upToDate = true;
    }).on('error', function (err) {
      console.log('error during replication with skimdb');
      console.error(err);
      // just keep going
      setTimeout(replicateForever, Math.round(startingTimeout * backoff));
    });
  }).catch(function (err) {
    console.log('error doing info() on skimdb');
    console.error(err);
  });
}
replicateForever();

// start doing exciting shit
Promise.resolve().then(function () {
  return skimPouch.get('_local/upToDate').catch(function (err) {
    if (err.status !== 404) {
      throw err;
    }
    return {}; // default doc
  });
}).then(function (doc) {
  upToDate = doc.upToDate;
  if (upToDate) {
    console.log('local skimdb is up to date');
  }
}).then(function () {
  // trick Fullfat into never doing automatic syncing; we don't use it for that
  return Promise.promisify(fs.writeFile)('registry.seq', '999999999999');
}).then(function () {
  fullFat = new Fullfat({
    skim: upToDate ? SKIM_LOCAL : SKIM_REMOTE,
    fat: FAT_LOCAL,
    seq_file: 'registry.seq',
    missing_log: 'missing.log'
  });
  fullFat.on('error', function (err) {
    console.log("fullfat hit an error");
    console.error(err);
  });
}).then(function () {
  // start user-facing proxy server

  // only execute n fullFat.js operations in parallel
  var NUM_PARALLEL = 5;
  var queues = [];
  for (var i = 0; i < NUM_PARALLEL; i++) {
    queues.push(Promise.resolve());
  }

  function processWithFullFat(docId) {
    console.log('fetching ' + docId + ' with fullFat.js');
    // recover by fetching with fullFat
    // wait for changes to let us know it was fetched
    var queueIdx = docId.charCodeAt(0) % queues.length;
    queues[queueIdx] = queues[queueIdx].then(function () {
      console.log('docId is ' + docId + ', looking up in pouch');
      return fatPouch.get(docId).catch(function (err) {
        console.log('pouch responded');
        if (err.status !== 404) {
          throw err;
        }
        return new Promise(function (resolve, reject) {
          var changes = fatPouch.changes({
            since: 'latest',
            live: true
          }).on('change', function (change) {
              console.log('got change: ' + change.id);
              if (change.id === docId) {
                console.log('successfully wrote to local fullfatdb: ' + docId);
                changes.cancel();
                changes.removeAllListeners('change'); // TODO: shouldn't have to do this
                resolve();
              }
            }).on('error', reject);
          console.log('telling fat to fetch it');
          fullFat.getDoc({id: docId, seq: Math.round(Math.random() * 1000000)});
        });
      });
    });
    return queues[queueIdx];
  }

  var server = require('http').createServer(function (req, res) {
    Promise.resolve().then(function () {
      if (req.method.toLowerCase() === 'get' &&
          /^\/fullfatdb\/([^\/]+)/.test(req.url)) {
        // simple doc or attachment request (get)
        console.log('simple doc request');
        var url = require('url').parse(req.url);
        var docId = decodeURIComponent(url.pathname.split('/')[2]);
        // check if it exists first
        return processWithFullFat(docId);
      }
    }).then(function (doc) {
      console.log('doc exists locally, using local fat');
      request.get('http://127.0.0.1:' + pouchPort + req.url).pipe(res);
    }).catch(function (err) {
      console.log('error, need to use remote fat instead');
      console.error(err);
      var url = req.url.replace(/^\/fullfatdb/, '');
      request.get(FAT_REMOTE + url).pipe(res);
    });
  });
  server.listen(5080, function (err) {
    if (err) {
      console.error(err);
    } else {
      console.log('Proxy server started at http://127.0.0.1:' + port);
    }
  });
}).catch(function (err) {
  console.error(err);
  throw err;
});

process.on('SIGINT', function () {
  // close gracefully
  sync.cancel();
  fatPouch.close().then(function () {
    return skimPouch.close();
  }).then(function () {
    process.exit(0);
  });
});<|endoftext|>


<commit_before>const cluster = require('cluster');
const uuid = require('uuid');
const LRUCache = require('lru-cache');

// lru caches by namespace on the master
const caches = {};

// track callbacks on the worker by request id
const callbacks = {};

// use to identify messages from our module
const source = 'lru-cache-for-clusters';

// only run on the master thread
if (cluster.isMaster) {
  // for each worker created...
  cluster.on('fork', (worker) => {
    // wait for the worker to send a message
    worker.on('message', (request) => {
      if (request.source !== source) return;

      // send a response back to the worker thread
      function sendResponse(data) {
        const response = data;
        response.source = source;
        response.id = request.id;
        response.func = request.func;
        worker.send(response);
      }

      // try to load an existing lru-cache
      let lru = caches[request.namespace];

      switch (request.func) {
        // constructor request
        case '()':
          // create a new lru-cache, give it a namespace, and save it locally
          lru = caches[request.namespace] = new LRUCache(...request.arguments);
          lru.namespace = request.namespace;
          sendResponse(lru);
          break;
        // return the property value
        case 'length':
        case 'itemCount':
          sendResponse({
            value: lru[request.func],
          });
          break;
        // return the function value
        default:
          sendResponse({
            value: lru[request.func](...request.arguments),
          });
          break;
      }
    });
  });
} else {
  process.on('message', (response) => {
    // look up the callback based on the response ID, delete it, then call it
    if (response.source !== source || !callbacks[response.id]) return;
    const callback = callbacks[response.id];
    delete callbacks[response.id];
    callback(response);
  });
}

/**
 * LRUCacheForClustersAsPromised roughly approximates the functionality of LRUCache
 * but in a promisified way. When running as a cluster workers send requests to the
 * master thread which actually holds the cache via IPC which then sends a response
 * that resolves the Promise. For non-clustered environments a Promisfied interface
 * to the cache is provided to match the interface for clustered environments.
 *
 * @param Object options The lru-cache options. Properties can be set, functions cannot.
 */
function LRUCacheForClustersAsPromised(options) {
  // keep a reference as 'this' is lost inside the Promise contexts
  const cache = this;

  // if this is the master thread, we just promisify an lru-cache
  const lru = cluster.isMaster ? new LRUCache(options) : null;

  // this is how the clustered cache differentiates
  cache.namespace = options.namespace || 'default';

  // return a promise that resolves to the result of the method on
  // the local lru-cache this is the master thread, or from the
  // lru-cache on the master thread if this is a worker
  const promiseTo = (...args) => {
    // first argument is the function to run
    const func = args[0];
    // the rest of the args are the function arguments of N length
    const funcArgs = Array.prototype.slice.call(args, 1, args.length);
    if (cluster.isMaster) {
      // just call the function on the lru-cache
      switch (func) {
        case 'itemCount':
        case 'length':
          return Promise.resolve(lru[func]);
          break;
        default:
          return Promise.resolve(lru[func](...funcArgs));
          break;
      }
    }
    return new Promise((resolve) => {
      // create the request to the master
      const request = {
        source,
        namespace: cache.namespace,
        id: uuid.v4(),
        func,
        arguments: funcArgs,
      };
      // if we don't get a response in 100ms, return undefined
      let failSafe = setTimeout(() => {
        failSafe = undefined;
        resolve(undefined);
      }, 100);
      // set the callback for this id to resolve the promise
      callbacks[request.id] = (result) =>
        (!failSafe || clearTimeout(failSafe) || resolve(result.value));
      // send the request to the master process
      process.send(request);
    });
  };

  if (cluster.isWorker) {
    // create a new LRU cache on the master
    promiseTo('()', options);
  }

  // the lru-cache functions we are able to provide. Note that length()
  // and itemCount() are functions and not properties. All functions
  // return a Promise.
  return {
    set: (key, value) => promiseTo('set', key, value),
    get: (key) => promiseTo('get', key),
    peek: (key) => promiseTo('peek', key),
    del: (key) => promiseTo('del', key),
    has: (key) => promiseTo('has', key),
    reset: () => promiseTo('reset'),
    keys: () => promiseTo('keys'),
    values: () => promiseTo('values'),
    dump: () => promiseTo('dump'),
    prune: () => promiseTo('prune'),
    length: () => promiseTo('length'),
    itemCount: () => promiseTo('itemCount'),
  };
}

module.exports = LRUCacheForClustersAsPromised;
<commit_msg>add more comments<commit_after>const cluster = require('cluster');
const uuid = require('uuid');
const LRUCache = require('lru-cache');

// lru caches by namespace on the master
const caches = {};

// track callbacks on the worker by request id
const callbacks = {};

// use to identify messages from our module
const source = 'lru-cache-for-clusters';

// only run on the master thread
if (cluster.isMaster) {
  // for each worker created...
  cluster.on('fork', (worker) => {
    // wait for the worker to send a message
    worker.on('message', (request) => {
      if (request.source !== source) return;

      // send a response back to the worker thread
      function sendResponse(data) {
        const response = data;
        response.source = source;
        response.id = request.id;
        response.func = request.func;
        worker.send(response);
      }

      // try to load an existing lru-cache
      let lru = caches[request.namespace];

      switch (request.func) {
        // constructor request
        case '()':
          // create a new lru-cache, give it a namespace, and save it locally
          lru = caches[request.namespace] = new LRUCache(...request.arguments);
          lru.namespace = request.namespace;
          sendResponse(lru);
          break;
        // return the property value
        case 'length':
        case 'itemCount':
          sendResponse({
            value: lru[request.func],
          });
          break;
        // return the function value
        default:
          sendResponse({
            value: lru[request.func](...request.arguments),
          });
          break;
      }
    });
  });
} else {
  process.on('message', (response) => {
    // look up the callback based on the response ID, delete it, then call it
    if (response.source !== source || !callbacks[response.id]) return;
    const callback = callbacks[response.id];
    delete callbacks[response.id];
    callback(response);
  });
}

/**
 * LRUCacheForClustersAsPromised roughly approximates the functionality of LRUCache
 * but in a promisified way. When running as a cluster workers send requests to the
 * master thread which actually holds the cache via IPC which then sends a response
 * that resolves the Promise. For non-clustered environments a Promisfied interface
 * to the cache is provided to match the interface for clustered environments.
 *
 * @param Object options The lru-cache options. Properties can be set, functions cannot.
 */
function LRUCacheForClustersAsPromised(options) {
  // keep a reference as 'this' is lost inside the Promise contexts
  const cache = this;

  // if this is the master thread, we just promisify an lru-cache
  const lru = cluster.isMaster ? new LRUCache(options) : null;

  // this is how the clustered cache differentiates
  cache.namespace = options.namespace || 'default';

  // return a promise that resolves to the result of the method on
  // the local lru-cache this is the master thread, or from the
  // lru-cache on the master thread if this is a worker
  const promiseTo = (...args) => {
    // first argument is the function to run
    const func = args[0];
    // the rest of the args are the function arguments of N length
    const funcArgs = Array.prototype.slice.call(args, 1, args.length);
    if (cluster.isMaster) {
      // act on the local lru-cache
      switch (func) {
        case 'itemCount':
        case 'length':
          // return the property value
          return Promise.resolve(lru[func]);
          break;
        default:
          // just call the function on the lru-cache
          return Promise.resolve(lru[func](...funcArgs));
          break;
      }
    }
    return new Promise((resolve) => {
      // create the request to the master
      const request = {
        source,
        namespace: cache.namespace,
        id: uuid.v4(),
        func,
        arguments: funcArgs,
      };
      // if we don't get a response in 100ms, return undefined
      let isFailed = setTimeout(() => {
        isFailed = undefined;
        resolve(undefined);
      }, 100);
      // set the callback for this id to resolve the promise
      callbacks[request.id] = (result) =>
        (!isFailed || clearTimeout(isFailed) || resolve(result.value));
      // send the request to the master process
      process.send(request);
    });
  };

  if (cluster.isWorker) {
    // create a new LRU cache on the master
    promiseTo('()', options);
  }

  // the lru-cache functions we are able to provide. Note that length()
  // and itemCount() are functions and not properties. All functions
  // return a Promise.
  return {
    set: (key, value) => promiseTo('set', key, value),
    get: (key) => promiseTo('get', key),
    peek: (key) => promiseTo('peek', key),
    del: (key) => promiseTo('del', key),
    has: (key) => promiseTo('has', key),
    reset: () => promiseTo('reset'),
    keys: () => promiseTo('keys'),
    values: () => promiseTo('values'),
    dump: () => promiseTo('dump'),
    prune: () => promiseTo('prune'),
    length: () => promiseTo('length'),
    itemCount: () => promiseTo('itemCount'),
  };
}

module.exports = LRUCacheForClustersAsPromised;
<|endoftext|>


<commit_before>const path = require('path')
const debug = require('debug')('metalsmith-auto-collections')
const msCollections = require('metalsmith-collections')
const mm = require('micromatch')

const auto_collect = ({ pattern = '**', settings = {}, collections = {} }) => {
  return (files, metalsmith, done) => {
    setImmediate(done)

    // initialize a container for all the collections
    // we use this so we can apply the settings for each collection
    // when we call metalsmith-collections
    const config = {}

    // Add non-automatic collections to config
    Object.keys(collections).forEach((collection) => {
      if (!config[collection]) config[collection] = collections[collection]
    })

    Object.keys(files).forEach((file) => {
      if (mm(file, pattern).length) {
        // get name of parent directory
        let parent = path.dirname(file).split(path.sep).pop()

        // set parent to source file in config if file is in root
        parent = parent === '.' ? metalsmith._source : parent

        // set parent key on file
        files[file].parent =
          files[file].parent ||
          path.join(metalsmith._source, path.dirname(file))

        // create new key in metalsmith-collections config if it doesn't exist
        if (!config[parent]) config[parent] = settings

        // extend the file metadata, filtering out falsy collection names
        files[file].collection = [files[file].collection, parent].filter(
          (c) => c
        )

        debug(`${file} added to "${parent}" collection`)
      }
    })

    // call metalsmith-collections
    debug(`metalsmith-collections configuration: ${config}`)
    msCollections(config)(files, metalsmith, done)
  }
}

module.exports = auto_collect
<commit_msg>:art: colocate file metadata manipulation<commit_after>const path = require('path')
const debug = require('debug')('metalsmith-auto-collections')
const msCollections = require('metalsmith-collections')
const mm = require('micromatch')

const auto_collect = ({ pattern = '**', settings = {}, collections = {} }) => {
  return (files, metalsmith, done) => {
    setImmediate(done)

    // initialize a container for all the collections
    // we use this so we can apply the settings for each collection
    // when we call metalsmith-collections
    const config = {}

    // Add non-automatic collections to config
    Object.keys(collections).forEach((collection) => {
      if (!config[collection]) config[collection] = collections[collection]
    })

    Object.keys(files).forEach((file) => {
      if (mm(file, pattern).length) {
        // get name of parent directory
        let parent = path.dirname(file).split(path.sep).pop()

        // set parent to source file in config if file is in root
        parent = parent === '.' ? metalsmith._source : parent

        // create new key in metalsmith-collections config if it doesn't exist
        if (!config[parent]) config[parent] = settings

        // add parent metadata to file
        files[file].parent =
          files[file].parent ||
          path.join(metalsmith._source, path.dirname(file))

        // extend the file's collection metadata, filtering out falsy collection
        // names. this prevents automatic collections from overriding manually
        // set collections.
        files[file].collection = [files[file].collection, parent].filter(
          (c) => c
        )

        debug(`${file} added to "${parent}" collection`)
      }
    })

    // call metalsmith-collections
    debug(`metalsmith-collections configuration: ${config}`)
    msCollections(config)(files, metalsmith, done)
  }
}

module.exports = auto_collect
<|endoftext|>