<commit_before>/*! FileSaver.js
 *  A saveAs() FileSaver implementation.
 *  2014-01-24
 *
 *  By Eli Grey, http://eligrey.com
 *  License: X11/MIT
 *    See LICENSE.md
 */

/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

var saveAs = saveAs
  // IE 10+ (native saveAs)
  || (typeof navigator !== "undefined" &&
      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
  // Everyone else
  || (function(view) {
	"use strict";
	// IE <10 is explicitly unsupported
	if (typeof navigator !== "undefined" &&
	    /MSIE [1-9]\./.test(navigator.userAgent)) {
		return;
	}
	var
		  doc = view.document
		  // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
		, get_URL = function() {
			return view.URL || view.webkitURL || view;
		}
		, URL = view.URL || view.webkitURL || view
		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
		, can_use_save_link = !view.externalHost && "download" in save_link
		, click = function(node) {
			var event = doc.createEvent("MouseEvents");
			event.initMouseEvent(
				"click", true, false, view, 0, 0, 0, 0, 0
				, false, false, false, false, 0, null
			);
			node.dispatchEvent(event);
		}
		, webkit_req_fs = view.webkitRequestFileSystem
		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
		, throw_outside = function(ex) {
			(view.setImmediate || view.setTimeout)(function() {
				throw ex;
			}, 0);
		}
		, force_saveable_type = "application/octet-stream"
		, fs_min_size = 0
		, deletion_queue = []
		, process_deletion_queue = function() {
			var i = deletion_queue.length;
			while (i--) {
				var file = deletion_queue[i];
				if (typeof file === "string") { // file is an object URL
					URL.revokeObjectURL(file);
				} else { // file is a File
					file.remove();
				}
			}
			deletion_queue.length = 0; // clear queue
		}
		, dispatch = function(filesaver, event_types, event) {
			event_types = [].concat(event_types);
			var i = event_types.length;
			while (i--) {
				var listener = filesaver["on" + event_types[i]];
				if (typeof listener === "function") {
					try {
						listener.call(filesaver, event || filesaver);
					} catch (ex) {
						throw_outside(ex);
					}
				}
			}
		}
		, FileSaver = function(blob, name) {
			// First try a.download, then web filesystem, then object URLs
			var
				  filesaver = this
				, type = blob.type
				, blob_changed = false
				, object_url
				, target_view
				, get_object_url = function() {
					var object_url = get_URL().createObjectURL(blob);
					deletion_queue.push(object_url);
					return object_url;
				}
				, dispatch_all = function() {
					dispatch(filesaver, "writestart progress write writeend".split(" "));
				}
				// on any filesys errors revert to saving with object URLs
				, fs_error = function() {
					// don't create more object URLs than needed
					if (blob_changed || !object_url) {
						object_url = get_object_url(blob);
					}
					if (target_view) {
						target_view.location.href = object_url;
					} else {
						window.open(object_url, "_blank");
					}
					filesaver.readyState = filesaver.DONE;
					dispatch_all();
				}
				, abortable = function(func) {
					return function() {
						if (filesaver.readyState !== filesaver.DONE) {
							return func.apply(this, arguments);
						}
					};
				}
				, create_if_not_found = {create: true, exclusive: false}
				, slice
			;
			filesaver.readyState = filesaver.INIT;
			if (!name) {
				name = "download";
			}
			if (can_use_save_link) {
				object_url = get_object_url(blob);
				// FF for Android has a nasty garbage collection mechanism
				// that turns all objects that are not pure javascript into 'deadObject'
				// this means `doc` and `save_link` are unusable and need to be recreated
				// `view` is usable though:
				doc = view.document;
				save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
				save_link.href = object_url;
				save_link.download = name;
				var event = doc.createEvent("MouseEvents");
				event.initMouseEvent(
					"click", true, false, view, 0, 0, 0, 0, 0
					, false, false, false, false, 0, null
				);
				save_link.dispatchEvent(event);
				filesaver.readyState = filesaver.DONE;
				dispatch_all();
				return;
			}
			// Object and web filesystem URLs have a problem saving in Google Chrome when
			// viewed in a tab, so I force save with application/octet-stream
			// http://code.google.com/p/chromium/issues/detail?id=91158
			if (view.chrome && type && type !== force_saveable_type) {
				slice = blob.slice || blob.webkitSlice;
				blob = slice.call(blob, 0, blob.size, force_saveable_type);
				blob_changed = true;
			}
			// Since I can't be sure that the guessed media type will trigger a download
			// in WebKit, I append .download to the filename.
			// https://bugs.webkit.org/show_bug.cgi?id=65440
			if (webkit_req_fs && name !== "download") {
				name += ".download";
			}
			if (type === force_saveable_type || webkit_req_fs) {
				target_view = view;
			}
			if (!req_fs) {
				fs_error();
				return;
			}
			fs_min_size += blob.size;
			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
					var save = function() {
						dir.getFile(name, create_if_not_found, abortable(function(file) {
							file.createWriter(abortable(function(writer) {
								writer.onwriteend = function(event) {
									target_view.location.href = file.toURL();
									deletion_queue.push(file);
									filesaver.readyState = filesaver.DONE;
									dispatch(filesaver, "writeend", event);
								};
								writer.onerror = function() {
									var error = writer.error;
									if (error.code !== error.ABORT_ERR) {
										fs_error();
									}
								};
								"writestart progress write abort".split(" ").forEach(function(event) {
									writer["on" + event] = filesaver["on" + event];
								});
								writer.write(blob);
								filesaver.abort = function() {
									writer.abort();
									filesaver.readyState = filesaver.DONE;
								};
								filesaver.readyState = filesaver.WRITING;
							}), fs_error);
						}), fs_error);
					};
					dir.getFile(name, {create: false}, abortable(function(file) {
						// delete file if it already exists
						file.remove();
						save();
					}), abortable(function(ex) {
						if (ex.code === ex.NOT_FOUND_ERR) {
							save();
						} else {
							fs_error();
						}
					}));
				}), fs_error);
			}), fs_error);
		}
		, FS_proto = FileSaver.prototype
		, saveAs = function(blob, name) {
			return new FileSaver(blob, name);
		}
	;
	FS_proto.abort = function() {
		var filesaver = this;
		filesaver.readyState = filesaver.DONE;
		dispatch(filesaver, "abort");
	};
	FS_proto.readyState = FS_proto.INIT = 0;
	FS_proto.WRITING = 1;
	FS_proto.DONE = 2;

	FS_proto.error =
	FS_proto.onwritestart =
	FS_proto.onprogress =
	FS_proto.onwrite =
	FS_proto.onabort =
	FS_proto.onerror =
	FS_proto.onwriteend =
		null;

	view.addEventListener("unload", process_deletion_queue, false);
	saveAs.unload = function() {
		process_deletion_queue();
		view.removeEventListener("unload", process_deletion_queue, false);
	};
	return saveAs;
}(
	   typeof self !== "undefined" && self
	|| typeof window !== "undefined" && window
	|| this.content
));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window

if (typeof module !== "undefined") module.exports = saveAs;
<commit_msg>FileSaver.js: add license to <commit_after>/*! FileSaver.js
 *  A saveAs() FileSaver implementation.
 *  2014-01-24
 *
 *  By Eli Grey, http://eligrey.com
 *  License: X11/MIT
 *
Copyright © 2015 Eli Grey.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
 to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 DEALINGS IN THE SOFTWARE.

 */

/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

var saveAs = saveAs
  // IE 10+ (native saveAs)
  || (typeof navigator !== "undefined" &&
      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
  // Everyone else
  || (function(view) {
	"use strict";
	// IE <10 is explicitly unsupported
	if (typeof navigator !== "undefined" &&
	    /MSIE [1-9]\./.test(navigator.userAgent)) {
		return;
	}
	var
		  doc = view.document
		  // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
		, get_URL = function() {
			return view.URL || view.webkitURL || view;
		}
		, URL = view.URL || view.webkitURL || view
		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
		, can_use_save_link = !view.externalHost && "download" in save_link
		, click = function(node) {
			var event = doc.createEvent("MouseEvents");
			event.initMouseEvent(
				"click", true, false, view, 0, 0, 0, 0, 0
				, false, false, false, false, 0, null
			);
			node.dispatchEvent(event);
		}
		, webkit_req_fs = view.webkitRequestFileSystem
		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
		, throw_outside = function(ex) {
			(view.setImmediate || view.setTimeout)(function() {
				throw ex;
			}, 0);
		}
		, force_saveable_type = "application/octet-stream"
		, fs_min_size = 0
		, deletion_queue = []
		, process_deletion_queue = function() {
			var i = deletion_queue.length;
			while (i--) {
				var file = deletion_queue[i];
				if (typeof file === "string") { // file is an object URL
					URL.revokeObjectURL(file);
				} else { // file is a File
					file.remove();
				}
			}
			deletion_queue.length = 0; // clear queue
		}
		, dispatch = function(filesaver, event_types, event) {
			event_types = [].concat(event_types);
			var i = event_types.length;
			while (i--) {
				var listener = filesaver["on" + event_types[i]];
				if (typeof listener === "function") {
					try {
						listener.call(filesaver, event || filesaver);
					} catch (ex) {
						throw_outside(ex);
					}
				}
			}
		}
		, FileSaver = function(blob, name) {
			// First try a.download, then web filesystem, then object URLs
			var
				  filesaver = this
				, type = blob.type
				, blob_changed = false
				, object_url
				, target_view
				, get_object_url = function() {
					var object_url = get_URL().createObjectURL(blob);
					deletion_queue.push(object_url);
					return object_url;
				}
				, dispatch_all = function() {
					dispatch(filesaver, "writestart progress write writeend".split(" "));
				}
				// on any filesys errors revert to saving with object URLs
				, fs_error = function() {
					// don't create more object URLs than needed
					if (blob_changed || !object_url) {
						object_url = get_object_url(blob);
					}
					if (target_view) {
						target_view.location.href = object_url;
					} else {
						window.open(object_url, "_blank");
					}
					filesaver.readyState = filesaver.DONE;
					dispatch_all();
				}
				, abortable = function(func) {
					return function() {
						if (filesaver.readyState !== filesaver.DONE) {
							return func.apply(this, arguments);
						}
					};
				}
				, create_if_not_found = {create: true, exclusive: false}
				, slice
			;
			filesaver.readyState = filesaver.INIT;
			if (!name) {
				name = "download";
			}
			if (can_use_save_link) {
				object_url = get_object_url(blob);
				// FF for Android has a nasty garbage collection mechanism
				// that turns all objects that are not pure javascript into 'deadObject'
				// this means `doc` and `save_link` are unusable and need to be recreated
				// `view` is usable though:
				doc = view.document;
				save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
				save_link.href = object_url;
				save_link.download = name;
				var event = doc.createEvent("MouseEvents");
				event.initMouseEvent(
					"click", true, false, view, 0, 0, 0, 0, 0
					, false, false, false, false, 0, null
				);
				save_link.dispatchEvent(event);
				filesaver.readyState = filesaver.DONE;
				dispatch_all();
				return;
			}
			// Object and web filesystem URLs have a problem saving in Google Chrome when
			// viewed in a tab, so I force save with application/octet-stream
			// http://code.google.com/p/chromium/issues/detail?id=91158
			if (view.chrome && type && type !== force_saveable_type) {
				slice = blob.slice || blob.webkitSlice;
				blob = slice.call(blob, 0, blob.size, force_saveable_type);
				blob_changed = true;
			}
			// Since I can't be sure that the guessed media type will trigger a download
			// in WebKit, I append .download to the filename.
			// https://bugs.webkit.org/show_bug.cgi?id=65440
			if (webkit_req_fs && name !== "download") {
				name += ".download";
			}
			if (type === force_saveable_type || webkit_req_fs) {
				target_view = view;
			}
			if (!req_fs) {
				fs_error();
				return;
			}
			fs_min_size += blob.size;
			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
					var save = function() {
						dir.getFile(name, create_if_not_found, abortable(function(file) {
							file.createWriter(abortable(function(writer) {
								writer.onwriteend = function(event) {
									target_view.location.href = file.toURL();
									deletion_queue.push(file);
									filesaver.readyState = filesaver.DONE;
									dispatch(filesaver, "writeend", event);
								};
								writer.onerror = function() {
									var error = writer.error;
									if (error.code !== error.ABORT_ERR) {
										fs_error();
									}
								};
								"writestart progress write abort".split(" ").forEach(function(event) {
									writer["on" + event] = filesaver["on" + event];
								});
								writer.write(blob);
								filesaver.abort = function() {
									writer.abort();
									filesaver.readyState = filesaver.DONE;
								};
								filesaver.readyState = filesaver.WRITING;
							}), fs_error);
						}), fs_error);
					};
					dir.getFile(name, {create: false}, abortable(function(file) {
						// delete file if it already exists
						file.remove();
						save();
					}), abortable(function(ex) {
						if (ex.code === ex.NOT_FOUND_ERR) {
							save();
						} else {
							fs_error();
						}
					}));
				}), fs_error);
			}), fs_error);
		}
		, FS_proto = FileSaver.prototype
		, saveAs = function(blob, name) {
			return new FileSaver(blob, name);
		}
	;
	FS_proto.abort = function() {
		var filesaver = this;
		filesaver.readyState = filesaver.DONE;
		dispatch(filesaver, "abort");
	};
	FS_proto.readyState = FS_proto.INIT = 0;
	FS_proto.WRITING = 1;
	FS_proto.DONE = 2;

	FS_proto.error =
	FS_proto.onwritestart =
	FS_proto.onprogress =
	FS_proto.onwrite =
	FS_proto.onabort =
	FS_proto.onerror =
	FS_proto.onwriteend =
		null;

	view.addEventListener("unload", process_deletion_queue, false);
	saveAs.unload = function() {
		process_deletion_queue();
		view.removeEventListener("unload", process_deletion_queue, false);
	};
	return saveAs;
}(
	   typeof self !== "undefined" && self
	|| typeof window !== "undefined" && window
	|| this.content
));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window

if (typeof module !== "undefined") module.exports = saveAs;
<|endoftext|>


<commit_before>import View from "./View";

export default class ListView extends View
{
    init()
    {
        super.init();
        this._items = null;
        this._selection = null;
        this._$itemTemplates = [];
        this.addStyleClass("nju-list-view");

        this._initLayout();

        this.$container.on("click", this.getItemElementTag(), this._onclick.bind(this))
    }

    _initLayout()
    {

    }

    getElementTag()
    {
        return "ul";
    }

    getItemElementTag()
    {
        return "li";
    }

    get items()
    {
        return this._items;
    }
    set items(value)
    {
        this.clearItems();
        this.addItems(value);
    }

    get selection()
    {
        return this._selection;
    }
    set selection(value)
    {
        this.selectItem(value);
    }

    getTypeOfItem(item)
    {
        return 0;
    }

    getIdOfItem(item)
    {
        return item.id;
    }

    clearItems()
    {
        if (this._items !== null)
        {
            if(this._items.length > 0)
            {
                this._items.splice(0, this._items.length);
                this.$container.children(this.getItemElementTag()).remove();
            }
        }
        else
        {
            this._items = [];
        }
    }

    addItems(items)
    {
        if (items && items.length)
        {
            items.forEach(item => {
                this.addItem(item);
            });
        }
    }

    addItem(item)
    {
        this.items.push(item);

        const $item = this.$createItem(this.getTypeOfItem(item));
        this.renderItem(item, $item);
        this.$container.append($item);
    }

    selectItem(item = null)
    {
        if (this.selection === item) return;

        if (this.selection !== null)
        {
            this.$getItem(this.selection).removeClass("selected");
            this._selection = null;
        }

        this._selection = item;

        if (item)
        {
            const $item = this.$getItem(item);
            $item.addClass("selected");
        }

    }

    renderItem(item, $item)
    {
        $item.data("item", item);
        $item.attr("id", "i-" + this.getIdOfItem(item));
    }

    $createItem(itemType = 0)
    {
        if (!this._$itemTemplates[itemType])
        {
            this._$itemTemplates[itemType] = this.$createNewItem(itemType);
        }
        return this._$itemTemplates[itemType].clone();
    }

    $createNewItem(itemType = 0)
    {
        return $(`<${this.getItemElementTag()}/>`);
    }

    $getItem(item)
    {
        const id = this.getIdOfItem(item);
        return this.$container.children("#i-" + id);
    }

    _onclick(e)
    {
        const $item = $(e.currentTarget);
        const item = $item.data("item");
        this.selectItem(item);
    }
}
<commit_msg>Add onselection event.<commit_after>import View from "./View";

export default class ListView extends View
{
    init()
    {
        super.init();
        this._items = null;
        this._selection = null;
        this._$itemTemplates = [];
        this.addStyleClass("nju-list-view");

        this._initLayout();

        this.$container.on("click", this.getItemElementTag(), this._onclick.bind(this))
    }

    _initLayout()
    {

    }

    getElementTag()
    {
        return "ul";
    }

    getItemElementTag()
    {
        return "li";
    }

    get items()
    {
        return this._items;
    }
    set items(value)
    {
        this.clearItems();
        this.addItems(value);
    }

    get selection()
    {
        return this._selection;
    }
    set selection(value)
    {
        this.selectItem(value);
    }

    getTypeOfItem(item)
    {
        return 0;
    }

    getIdOfItem(item)
    {
        return item.id;
    }

    clearItems()
    {
        if (this._items !== null)
        {
            if(this._items.length > 0)
            {
                this._items.splice(0, this._items.length);
                this.$container.children(this.getItemElementTag()).remove();
            }
        }
        else
        {
            this._items = [];
        }
    }

    addItems(items)
    {
        if (items && items.length)
        {
            items.forEach(item => {
                this.addItem(item);
            });
        }
    }

    addItem(item)
    {
        this.items.push(item);

        const $item = this.$createItem(this.getTypeOfItem(item));
        this.renderItem(item, $item);
        this.$container.append($item);
    }

    selectItem(item = null)
    {
        if (this.selection === item) return;

        if (this.selection !== null)
        {
            this.$getItem(this.selection).removeClass("selected");
            this._selection = null;
        }

        this._selection = item;

        if (item)
        {
            const $item = this.$getItem(item);
            $item.addClass("selected");
        }

        this.trigger("selectionchanged");
    }

    renderItem(item, $item)
    {
        $item.data("item", item);
        $item.attr("id", "i-" + this.getIdOfItem(item));
    }

    $createItem(itemType = 0)
    {
        if (!this._$itemTemplates[itemType])
        {
            this._$itemTemplates[itemType] = this.$createNewItem(itemType);
        }
        return this._$itemTemplates[itemType].clone();
    }

    $createNewItem(itemType = 0)
    {
        return $(`<${this.getItemElementTag()}/>`);
    }

    $getItem(item)
    {
        const id = this.getIdOfItem(item);
        return this.$container.children("#i-" + id);
    }

    _onclick(e)
    {
        const $item = $(e.currentTarget);
        const item = $item.data("item");
        this.selectItem(item);
    }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * This Module manages all /minified/* requests. It controls the
 * minification && compression of Javascript and CSS.
 */

/*
 * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const assert = require('assert').strict;
const settings = require('./Settings');
const fs = require('fs').promises;
const path = require('path');
const plugins = require('../../static/js/pluginfw/plugin_defs');
const RequireKernel = require('etherpad-require-kernel');
const mime = require('mime-types');
const Threads = require('threads');
const log4js = require('log4js');

const logger = log4js.getLogger('Minify');

const ROOT_DIR = path.join(settings.root, 'src/static/');

const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);

const LIBRARY_WHITELIST = [
  'async',
  'js-cookie',
  'security',
  'tinycon',
  'underscore',
  'unorm',
];

// What follows is a terrible hack to avoid loop-back within the server.
// TODO: Serve files from another service, or directly from the file system.
const requestURI = async (url, method, headers) => await new Promise((resolve, reject) => {
  const parsedUrl = new URL(url);
  let status = 500;
  const content = [];
  const mockRequest = {
    url,
    method,
    params: {filename: (parsedUrl.pathname + parsedUrl.search).replace(/^\/static\//, '')},
    headers,
  };
  const mockResponse = {
    writeHead: (_status, _headers) => {
      status = _status;
      for (const header in _headers) {
        if (Object.prototype.hasOwnProperty.call(_headers, header)) {
          headers[header] = _headers[header];
        }
      }
    },
    setHeader: (header, value) => {
      headers[header.toLowerCase()] = value.toString();
    },
    header: (header, value) => {
      headers[header.toLowerCase()] = value.toString();
    },
    write: (_content) => {
      _content && content.push(_content);
    },
    end: (_content) => {
      _content && content.push(_content);
      resolve([status, headers, content.join('')]);
    },
  };
  minify(mockRequest, mockResponse).catch(reject);
});

const requestURIs = (locations, method, headers, callback) => {
  Promise.all(locations.map((loc) => requestURI(loc, method, headers))).then((responses) => {
    const statuss = responses.map((x) => x[0]);
    const headerss = responses.map((x) => x[1]);
    const contentss = responses.map((x) => x[2]);
    callback(statuss, headerss, contentss);
  });
};

const sanitizePathname = (p) => {
  // Replace all backslashes with forward slashes to support Windows. This MUST be done BEFORE path
  // normalization, otherwise an attacker will be able to read arbitrary files anywhere on the
  // filesystem. See https://nvd.nist.gov/vuln/detail/CVE-2015-3297. Node.js treats both the
  // backlash and the forward slash characters as pathname component separators on Windows so this
  // does not change the meaning of the pathname.
  p = p.replace(/\\/g, '/');
  // The Node.js documentation says that path.join() normalizes, and the documentation for
  // path.normalize() says that it resolves '..' and '.' components. The word "resolve" implies that
  // it examines the filesystem to resolve symbolic links, so 'a/../b' might not be the same thing
  // as 'b'. Most path normalization functions from other libraries (e.g. Python's
  // os.path.normpath()) clearly state that they do not examine the filesystem -- they are simple
  // string manipulations. Node.js's path.normalize() probably also does a simple string
  // manipulation, but if not it must be given a real pathname. Join with ROOT_DIR here just in
  // case. ROOT_DIR will be removed later.
  p = path.join(ROOT_DIR, p);
  // Prevent attempts to read outside of ROOT_DIR via extra '..' components. ROOT_DIR is assumed to
  // be normalized.
  assert(ROOT_DIR.endsWith(path.sep));
  if (!p.startsWith(ROOT_DIR)) throw new Error(`attempt to read outside ROOT_DIR (${ROOT_DIR})`);
  // Convert back to a relative pathname.
  p = p.slice(ROOT_DIR.length);
  // On Windows, path.normalize replaces forward slashes with backslashes. Convert back to forward
  // slashes. THIS IS DANGEROUS UNLESS BACKSLASHES ARE REPLACED WITH FORWARD SLASHES BEFORE PATH
  // NORMALIZATION, otherwise on POSIXish systems '..\\' in the input pathname would not be
  // normalized away before being converted to '../'.
  p = p.replace(/\\/g, '/');
  return p;
};

/**
 * creates the minifed javascript for the given minified name
 * @param req the Express request
 * @param res the Express response
 */
const minify = async (req, res) => {
  let filename = req.params.filename;
  try {
    filename = sanitizePathname(filename);
  } catch (err) {
    logger.error(`sanitization of pathname "${filename}" failed: ${err.stack || err}`);
    res.writeHead(404, {});
    res.end();
    return;
  }

  // Backward compatibility for plugins that were written when jQuery lived at
  // src/static/js/jquery.js.
  if (['js/jquery.js', 'plugins/ep_etherpad-lite/static/js/jquery.js'].indexOf(filename) !== -1) {
    logger.warn(`request for deprecated jQuery path: ${filename}`);
    filename = 'js/vendors/jquery.js';
  }

  /* Handle static files for plugins/libraries:
     paths like "plugins/ep_myplugin/static/js/test.js"
     are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js,
     commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js
  */
  const match = filename.match(/^plugins\/([^/]+)(\/(?:(static\/.*)|.*))?$/);
  if (match) {
    const library = match[1];
    const libraryPath = match[2] || '';

    if (plugins.plugins[library] && match[3]) {
      const plugin = plugins.plugins[library];
      const pluginPath = plugin.package.realPath;
      filename = path.relative(ROOT_DIR, pluginPath + libraryPath);
      // On Windows, path.relative converts forward slashes to backslashes. Convert them back
      // because some of the code below assumes forward slashes. Node.js treats both the backlash
      // and the forward slash characters as pathname component separators on Windows so this does
      // not change the meaning of the pathname. This conversion does not introduce a directory
      // traversal vulnerability because all '..\\' substrings have already been removed by
      // sanitizePathname.
      filename = filename.replace(/\\/g, '/');
    } else if (LIBRARY_WHITELIST.indexOf(library) !== -1) {
      // Go straight into node_modules
      // Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js'
      // would end up resolving to logically distinct resources.
      filename = `../node_modules/${library}${libraryPath}`;
    }
  }

  const contentType = mime.lookup(filename);

  const [date, exists] = await statFile(filename, 3);
  if (date) {
    date.setMilliseconds(0);
    res.setHeader('last-modified', date.toUTCString());
    res.setHeader('date', (new Date()).toUTCString());
    if (settings.maxAge !== undefined) {
      const expiresDate = new Date(Date.now() + settings.maxAge * 1000);
      res.setHeader('expires', expiresDate.toUTCString());
      res.setHeader('cache-control', `max-age=${settings.maxAge}`);
    }
  }

  if (!exists) {
    res.writeHead(404, {});
    res.end();
  } else if (new Date(req.headers['if-modified-since']) >= date) {
    res.writeHead(304, {});
    res.end();
  } else if (req.method === 'HEAD') {
    res.header('Content-Type', contentType);
    res.writeHead(200, {});
    res.end();
  } else if (req.method === 'GET') {
    const content = await getFileCompressed(filename, contentType);
    res.header('Content-Type', contentType);
    res.writeHead(200, {});
    res.write(content);
    res.end();
  } else {
    res.writeHead(405, {allow: 'HEAD, GET'});
    res.end();
  }
};

// find all includes in ace.js and embed them.
const getAceFile = async () => {
  let data = await fs.readFile(`${ROOT_DIR}js/ace.js`, 'utf8');

  // Find all includes in ace.js and embed them
  const filenames = [];
  if (settings.minify) {
    const regex = /\$\$INCLUDE_[a-zA-Z_]+\((['"])([^'"]*)\1\)/gi;
    // This logic can be simplified via String.prototype.matchAll() once support for Node.js
    // v11.x and older is dropped.
    let matches;
    while ((matches = regex.exec(data)) != null) {
      filenames.push(matches[2]);
    }
  }
  // Always include the require kernel.
  filenames.push('../static/js/require-kernel.js');

  data += ';\n';
  data += 'Ace2Editor.EMBEDED = Ace2Editor.EMBEDED || {};\n';

  // Request the contents of the included file on the server-side and write
  // them into the file.
  await Promise.all(filenames.map(async (filename) => {
    // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
    const baseURI = 'http://invalid.invalid';
    let resourceURI = baseURI + path.join('/static/', filename);
    resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)

    const [status, , body] = await requestURI(resourceURI, 'GET', {});
    const error = !(status === 200 || status === 404);
    if (!error) {
      data += `Ace2Editor.EMBEDED[${JSON.stringify(filename)}] = ${
        JSON.stringify(status === 200 ? body || '' : null)};\n`;
    } else {
      console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`);
    }
  }));
  return data;
};

// Check for the existance of the file and get the last modification date.
const statFile = async (filename, dirStatLimit) => {
  /*
   * The only external call to this function provides an explicit value for
   * dirStatLimit: this check could be removed.
   */
  if (typeof dirStatLimit === 'undefined') {
    dirStatLimit = 3;
  }

  if (dirStatLimit < 1 || filename === '' || filename === '/') {
    return [null, false];
  } else if (filename === 'js/ace.js') {
    // Sometimes static assets are inlined into this file, so we have to stat
    // everything.
    return [await lastModifiedDateOfEverything(), true];
  } else if (filename === 'js/require-kernel.js') {
    return [_requireLastModified, true];
  } else {
    let stats;
    try {
      stats = await fs.stat(ROOT_DIR + filename);
    } catch (err) {
      if (err.code === 'ENOENT') {
        // Stat the directory instead.
        const [date] = await statFile(path.dirname(filename), dirStatLimit - 1);
        return [date, false];
      }
      throw err;
    }
    return [stats.mtime, stats.isFile()];
  }
};

const lastModifiedDateOfEverything = async () => {
  const folders2check = [`${ROOT_DIR}js/`, `${ROOT_DIR}css/`];
  let latestModification = null;
  // go through this two folders
  await Promise.all(folders2check.map(async (path) => {
    // read the files in the folder
    const files = await fs.readdir(path);

    // we wanna check the directory itself for changes too
    files.push('.');

    // go through all files in this folder
    await Promise.all(files.map(async (filename) => {
      // get the stat data of this file
      const stats = await fs.stat(`${path}/${filename}`);

      // compare the modification time to the highest found
      if (latestModification == null || stats.mtime > latestModification) {
        latestModification = stats.mtime;
      }
    }));
  }));
  return latestModification;
};

// This should be provided by the module, but until then, just use startup
// time.
const _requireLastModified = new Date();
const requireDefinition = () => `var require = ${RequireKernel.kernelSource};\n`;

const getFileCompressed = async (filename, contentType) => {
  let content = await getFile(filename);
  if (!content || !settings.minify) {
    return content;
  } else if (contentType === 'application/javascript') {
    return await new Promise((resolve) => {
      threadsPool.queue(async ({compressJS}) => {
        try {
          logger.info('Compress JS file %s.', filename);

          content = content.toString();
          const compressResult = await compressJS(content);

          if (compressResult.error) {
            console.error(`Error compressing JS (${filename}) using terser`, compressResult.error);
          } else {
            content = compressResult.code.toString(); // Convert content obj code to string
          }
        } catch (error) {
          console.error('getFile() returned an error in ' +
                        `getFileCompressed(${filename}, ${contentType}): ${error}`);
        }
        resolve(content);
      });
    });
  } else if (contentType === 'text/css') {
    return await new Promise((resolve) => {
      threadsPool.queue(async ({compressCSS}) => {
        try {
          logger.info('Compress CSS file %s.', filename);

          content = await compressCSS(filename, ROOT_DIR);
        } catch (error) {
          console.error(`CleanCSS.minify() returned an error on ${filename}: ${error}`);
        }
        resolve(content);
      });
    });
  } else {
    return content;
  }
};

const getFile = async (filename) => {
  if (filename === 'js/ace.js') return await getAceFile();
  if (filename === 'js/require-kernel.js') return requireDefinition();
  return await fs.readFile(ROOT_DIR + filename);
};

exports.minify = (req, res, next) => minify(req, res).catch((err) => next(err || new Error(err)));

exports.requestURIs = requestURIs;

exports.shutdown = async (hookName, context) => {
  await threadsPool.terminate();
};
<commit_msg>Minify: Consistently use `path.join()` to build pathnames<commit_after>'use strict';

/**
 * This Module manages all /minified/* requests. It controls the
 * minification && compression of Javascript and CSS.
 */

/*
 * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const assert = require('assert').strict;
const settings = require('./Settings');
const fs = require('fs').promises;
const path = require('path');
const plugins = require('../../static/js/pluginfw/plugin_defs');
const RequireKernel = require('etherpad-require-kernel');
const mime = require('mime-types');
const Threads = require('threads');
const log4js = require('log4js');

const logger = log4js.getLogger('Minify');

const ROOT_DIR = path.join(settings.root, 'src/static/');

const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);

const LIBRARY_WHITELIST = [
  'async',
  'js-cookie',
  'security',
  'tinycon',
  'underscore',
  'unorm',
];

// What follows is a terrible hack to avoid loop-back within the server.
// TODO: Serve files from another service, or directly from the file system.
const requestURI = async (url, method, headers) => await new Promise((resolve, reject) => {
  const parsedUrl = new URL(url);
  let status = 500;
  const content = [];
  const mockRequest = {
    url,
    method,
    params: {filename: (parsedUrl.pathname + parsedUrl.search).replace(/^\/static\//, '')},
    headers,
  };
  const mockResponse = {
    writeHead: (_status, _headers) => {
      status = _status;
      for (const header in _headers) {
        if (Object.prototype.hasOwnProperty.call(_headers, header)) {
          headers[header] = _headers[header];
        }
      }
    },
    setHeader: (header, value) => {
      headers[header.toLowerCase()] = value.toString();
    },
    header: (header, value) => {
      headers[header.toLowerCase()] = value.toString();
    },
    write: (_content) => {
      _content && content.push(_content);
    },
    end: (_content) => {
      _content && content.push(_content);
      resolve([status, headers, content.join('')]);
    },
  };
  minify(mockRequest, mockResponse).catch(reject);
});

const requestURIs = (locations, method, headers, callback) => {
  Promise.all(locations.map((loc) => requestURI(loc, method, headers))).then((responses) => {
    const statuss = responses.map((x) => x[0]);
    const headerss = responses.map((x) => x[1]);
    const contentss = responses.map((x) => x[2]);
    callback(statuss, headerss, contentss);
  });
};

const sanitizePathname = (p) => {
  // Replace all backslashes with forward slashes to support Windows. This MUST be done BEFORE path
  // normalization, otherwise an attacker will be able to read arbitrary files anywhere on the
  // filesystem. See https://nvd.nist.gov/vuln/detail/CVE-2015-3297. Node.js treats both the
  // backlash and the forward slash characters as pathname component separators on Windows so this
  // does not change the meaning of the pathname.
  p = p.replace(/\\/g, '/');
  // The Node.js documentation says that path.join() normalizes, and the documentation for
  // path.normalize() says that it resolves '..' and '.' components. The word "resolve" implies that
  // it examines the filesystem to resolve symbolic links, so 'a/../b' might not be the same thing
  // as 'b'. Most path normalization functions from other libraries (e.g. Python's
  // os.path.normpath()) clearly state that they do not examine the filesystem -- they are simple
  // string manipulations. Node.js's path.normalize() probably also does a simple string
  // manipulation, but if not it must be given a real pathname. Join with ROOT_DIR here just in
  // case. ROOT_DIR will be removed later.
  p = path.join(ROOT_DIR, p);
  // Prevent attempts to read outside of ROOT_DIR via extra '..' components. ROOT_DIR is assumed to
  // be normalized.
  assert(ROOT_DIR.endsWith(path.sep));
  if (!p.startsWith(ROOT_DIR)) throw new Error(`attempt to read outside ROOT_DIR (${ROOT_DIR})`);
  // Convert back to a relative pathname.
  p = p.slice(ROOT_DIR.length);
  // On Windows, path.normalize replaces forward slashes with backslashes. Convert back to forward
  // slashes. THIS IS DANGEROUS UNLESS BACKSLASHES ARE REPLACED WITH FORWARD SLASHES BEFORE PATH
  // NORMALIZATION, otherwise on POSIXish systems '..\\' in the input pathname would not be
  // normalized away before being converted to '../'.
  p = p.replace(/\\/g, '/');
  return p;
};

/**
 * creates the minifed javascript for the given minified name
 * @param req the Express request
 * @param res the Express response
 */
const minify = async (req, res) => {
  let filename = req.params.filename;
  try {
    filename = sanitizePathname(filename);
  } catch (err) {
    logger.error(`sanitization of pathname "${filename}" failed: ${err.stack || err}`);
    res.writeHead(404, {});
    res.end();
    return;
  }

  // Backward compatibility for plugins that were written when jQuery lived at
  // src/static/js/jquery.js.
  if (['js/jquery.js', 'plugins/ep_etherpad-lite/static/js/jquery.js'].indexOf(filename) !== -1) {
    logger.warn(`request for deprecated jQuery path: ${filename}`);
    filename = 'js/vendors/jquery.js';
  }

  /* Handle static files for plugins/libraries:
     paths like "plugins/ep_myplugin/static/js/test.js"
     are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js,
     commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js
  */
  const match = filename.match(/^plugins\/([^/]+)(\/(?:(static\/.*)|.*))?$/);
  if (match) {
    const library = match[1];
    const libraryPath = match[2] || '';

    if (plugins.plugins[library] && match[3]) {
      const plugin = plugins.plugins[library];
      const pluginPath = plugin.package.realPath;
      filename = path.relative(ROOT_DIR, path.join(pluginPath, libraryPath));
      // On Windows, path.relative converts forward slashes to backslashes. Convert them back
      // because some of the code below assumes forward slashes. Node.js treats both the backlash
      // and the forward slash characters as pathname component separators on Windows so this does
      // not change the meaning of the pathname. This conversion does not introduce a directory
      // traversal vulnerability because all '..\\' substrings have already been removed by
      // sanitizePathname.
      filename = filename.replace(/\\/g, '/');
    } else if (LIBRARY_WHITELIST.indexOf(library) !== -1) {
      // Go straight into node_modules
      // Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js'
      // would end up resolving to logically distinct resources.
      filename = path.join('../node_modules/', library, libraryPath);
    }
  }

  const contentType = mime.lookup(filename);

  const [date, exists] = await statFile(filename, 3);
  if (date) {
    date.setMilliseconds(0);
    res.setHeader('last-modified', date.toUTCString());
    res.setHeader('date', (new Date()).toUTCString());
    if (settings.maxAge !== undefined) {
      const expiresDate = new Date(Date.now() + settings.maxAge * 1000);
      res.setHeader('expires', expiresDate.toUTCString());
      res.setHeader('cache-control', `max-age=${settings.maxAge}`);
    }
  }

  if (!exists) {
    res.writeHead(404, {});
    res.end();
  } else if (new Date(req.headers['if-modified-since']) >= date) {
    res.writeHead(304, {});
    res.end();
  } else if (req.method === 'HEAD') {
    res.header('Content-Type', contentType);
    res.writeHead(200, {});
    res.end();
  } else if (req.method === 'GET') {
    const content = await getFileCompressed(filename, contentType);
    res.header('Content-Type', contentType);
    res.writeHead(200, {});
    res.write(content);
    res.end();
  } else {
    res.writeHead(405, {allow: 'HEAD, GET'});
    res.end();
  }
};

// find all includes in ace.js and embed them.
const getAceFile = async () => {
  let data = await fs.readFile(path.join(ROOT_DIR, 'js/ace.js'), 'utf8');

  // Find all includes in ace.js and embed them
  const filenames = [];
  if (settings.minify) {
    const regex = /\$\$INCLUDE_[a-zA-Z_]+\((['"])([^'"]*)\1\)/gi;
    // This logic can be simplified via String.prototype.matchAll() once support for Node.js
    // v11.x and older is dropped.
    let matches;
    while ((matches = regex.exec(data)) != null) {
      filenames.push(matches[2]);
    }
  }
  // Always include the require kernel.
  filenames.push('../static/js/require-kernel.js');

  data += ';\n';
  data += 'Ace2Editor.EMBEDED = Ace2Editor.EMBEDED || {};\n';

  // Request the contents of the included file on the server-side and write
  // them into the file.
  await Promise.all(filenames.map(async (filename) => {
    // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
    const baseURI = 'http://invalid.invalid';
    let resourceURI = baseURI + path.join('/static/', filename);
    resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)

    const [status, , body] = await requestURI(resourceURI, 'GET', {});
    const error = !(status === 200 || status === 404);
    if (!error) {
      data += `Ace2Editor.EMBEDED[${JSON.stringify(filename)}] = ${
        JSON.stringify(status === 200 ? body || '' : null)};\n`;
    } else {
      console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`);
    }
  }));
  return data;
};

// Check for the existance of the file and get the last modification date.
const statFile = async (filename, dirStatLimit) => {
  /*
   * The only external call to this function provides an explicit value for
   * dirStatLimit: this check could be removed.
   */
  if (typeof dirStatLimit === 'undefined') {
    dirStatLimit = 3;
  }

  if (dirStatLimit < 1 || filename === '' || filename === '/') {
    return [null, false];
  } else if (filename === 'js/ace.js') {
    // Sometimes static assets are inlined into this file, so we have to stat
    // everything.
    return [await lastModifiedDateOfEverything(), true];
  } else if (filename === 'js/require-kernel.js') {
    return [_requireLastModified, true];
  } else {
    let stats;
    try {
      stats = await fs.stat(path.join(ROOT_DIR, filename));
    } catch (err) {
      if (err.code === 'ENOENT') {
        // Stat the directory instead.
        const [date] = await statFile(path.dirname(filename), dirStatLimit - 1);
        return [date, false];
      }
      throw err;
    }
    return [stats.mtime, stats.isFile()];
  }
};

const lastModifiedDateOfEverything = async () => {
  const folders2check = [path.join(ROOT_DIR, 'js/'), path.join(ROOT_DIR, 'css/')];
  let latestModification = null;
  // go through this two folders
  await Promise.all(folders2check.map(async (dir) => {
    // read the files in the folder
    const files = await fs.readdir(dir);

    // we wanna check the directory itself for changes too
    files.push('.');

    // go through all files in this folder
    await Promise.all(files.map(async (filename) => {
      // get the stat data of this file
      const stats = await fs.stat(path.join(dir, filename));

      // compare the modification time to the highest found
      if (latestModification == null || stats.mtime > latestModification) {
        latestModification = stats.mtime;
      }
    }));
  }));
  return latestModification;
};

// This should be provided by the module, but until then, just use startup
// time.
const _requireLastModified = new Date();
const requireDefinition = () => `var require = ${RequireKernel.kernelSource};\n`;

const getFileCompressed = async (filename, contentType) => {
  let content = await getFile(filename);
  if (!content || !settings.minify) {
    return content;
  } else if (contentType === 'application/javascript') {
    return await new Promise((resolve) => {
      threadsPool.queue(async ({compressJS}) => {
        try {
          logger.info('Compress JS file %s.', filename);

          content = content.toString();
          const compressResult = await compressJS(content);

          if (compressResult.error) {
            console.error(`Error compressing JS (${filename}) using terser`, compressResult.error);
          } else {
            content = compressResult.code.toString(); // Convert content obj code to string
          }
        } catch (error) {
          console.error('getFile() returned an error in ' +
                        `getFileCompressed(${filename}, ${contentType}): ${error}`);
        }
        resolve(content);
      });
    });
  } else if (contentType === 'text/css') {
    return await new Promise((resolve) => {
      threadsPool.queue(async ({compressCSS}) => {
        try {
          logger.info('Compress CSS file %s.', filename);

          content = await compressCSS(filename, ROOT_DIR);
        } catch (error) {
          console.error(`CleanCSS.minify() returned an error on ${filename}: ${error}`);
        }
        resolve(content);
      });
    });
  } else {
    return content;
  }
};

const getFile = async (filename) => {
  if (filename === 'js/ace.js') return await getAceFile();
  if (filename === 'js/require-kernel.js') return requireDefinition();
  return await fs.readFile(path.join(ROOT_DIR, filename));
};

exports.minify = (req, res, next) => minify(req, res).catch((err) => next(err || new Error(err)));

exports.requestURIs = requestURIs;

exports.shutdown = async (hookName, context) => {
  await threadsPool.terminate();
};
<|endoftext|>


<commit_before>/**
 * @returns The absolute value of the number.
 */
Number.prototype.abs = function() {
  return Math.abs(this);
};

/**
 * @returns The number truncated to the nearest integer of greater than or equal value.
 *
 * (4.9).ceil(); // => 5
 * (4.2).ceil(); // => 5
 * (-1.2).ceil(); // => -1
 */
Number.prototype.ceil = function() {
  return Math.ceil(this);
};

/**
 * Returns a number whose value is limited to the given range.
 *
 * Example: limit the output of this computation to between 0 and 255
 *

 * (x * 255).clamp(0, 255)
 *

 *
 * @param {Number} min The lower boundary of the output range
 * @param {Number} max The upper boundary of the output range
 * @returns A number in the range [min, max]
 * @type Number
 */
Number.prototype.clamp = function(min, max) {
  return Math.min(Math.max(this, min), max);
};

/**
 * @returns The number truncated to the nearest integer of less than or equal value.
 *
 * (4.9).floor(); // => 4
 * (4.2).floor(); // => 4
 * (-1.2).floor(); // => -2
 */
Number.prototype.floor = function() {
  return Math.floor(this);
};

/**
 * A mod method useful for array wrapping. The range of the function is
 * constrained to remain in bounds of array indices.
 *
 *

 * Example:
 * (-1).mod(5) === 4
 *

 *
 * @param {Number} base
 * @returns An integer between 0 and (base - 1) if base is positive.
 * @type Number
 */
Number.prototype.mod = function(base) {
  var result = this % base;

  if(result < 0 && base > 0) {
    result += base;
  }

  return result;
};

/**
 * @returns The number rounded to the nearest integer.
 *
 * (4.5).round(); // => 5
 * (4.4).round(); // => 4
 */
Number.prototype.round = function() {
  return Math.round(this);
};

/**
 * @returns The sign of this number, 0 if the number is 0.
 */
Number.prototype.sign = function() {
  if(this > 0) {
    return 1;
  } else if (this < 0) {
    return -1;
  } else {
    return 0;
  }
};

/**
 * Calls iterator the specified number of times, passing in the number of the
 * current iteration as a parameter: 0 on first call, 1 on the second call, etc.
 *
 * @param {Function} iterator The iterator takes a single parameter, the number
 * of the current iteration.
 * @param {Object} [context] The optional context parameter specifies an object
 * to treat as this in the iterator block.
 *
 * @returns The number of times the iterator was called.
 * @type Number
 */
Number.prototype.times = function(iterator, context) {
  for(var i = 0; i < this; i++) {
    iterator.call(context, i);
  }

  return i;
};

/**
 * Returns the the nearest grid resolution less than or equal to the number.
 *
 *   EX:
 *    (7).snap(8) => 0
 *    (4).snap(8) => 0
 *    (12).snap(8) => 8
 *
 * @param {Number} resolution The grid resolution to snap to.
 * @returns The nearest multiple of resolution lower than the number.
 * @type Number
 */
Number.prototype.snap = function(resolution) {
  return (this / resolution).floor() * resolution;
};

Number.prototype.toColorPart = function() {
  var s = parseInt(this.clamp(0, 255), 10).toString(16);
  if(s.length == 1) {
    s = '0' + s;
  }

  return s;
};

Number.prototype.approach = function(target, maxDelta) {
  return (target - this).clamp(-maxDelta, maxDelta) + this;
};

Number.prototype.approachByRatio = function(target, ratio) {
  return this.approach(target, this * ratio);
};

Number.prototype.approachRotation = function(target, maxDelta) {
  var twoPi = 2 * Math.PI;

  while(target > this + Math.PI) {
    target -= twoPi
  }

  while(target < this - Math.PI) {
    target += twoPi
  }

  return (target - this).clamp(-maxDelta, maxDelta) + this;
};

/**
 * @returns This number constrained between -PI and PI.
 */
Number.prototype.constrainRotation = function() {
  var twoPi = 2 * Math.PI;

  var target = this;

  while(target > Math.PI) {
    target -= twoPi
  }

  while(target < -Math.PI) {
    target += twoPi
  }

  return target;
};

Number.prototype.d = function(sides) {
  var sum = 0;

  this.times(function() {
    sum += rand(sides) + 1;
  });

  return sum;
};

<commit_msg>Modified in browser at pixie.strd6.com<commit_after>/**
 * @returns The absolute value of the number.
 */
Number.prototype.abs = function() {
  return Math.abs(this);
};

/**
 * @returns The number truncated to the nearest integer of greater than or equal value.
 *
 * (4.9).ceil(); // => 5
 * (4.2).ceil(); // => 5
 * (-1.2).ceil(); // => -1
 */
Number.prototype.ceil = function() {
  return Math.ceil(this);
};

/**
 * Returns a number whose value is limited to the given range.
 *
 * Example: limit the output of this computation to between 0 and 255
 *

 * (x * 255).clamp(0, 255)
 *

 *
 * @param {Number} min The lower boundary of the output range
 * @param {Number} max The upper boundary of the output range
 * @returns A number in the range [min, max]
 * @type Number
 */
Number.prototype.clamp = function(min, max) {
  return Math.min(Math.max(this, min), max);
};

/**
 * @returns The number truncated to the nearest integer of less than or equal value.
 *
 * (4.9).floor(); // => 4
 * (4.2).floor(); // => 4
 * (-1.2).floor(); // => -2
 */
Number.prototype.floor = function() {
  return Math.floor(this);
};

/**
 * A mod method useful for array wrapping. The range of the function is
 * constrained to remain in bounds of array indices.
 *
 *

 * Example:
 * (-1).mod(5) === 4
 *

 *
 * @param {Number} base
 * @returns An integer between 0 and (base - 1) if base is positive.
 * @type Number
 */
Number.prototype.mod = function(base) {
  var result = this % base;

  if(result < 0 && base > 0) {
    result += base;
  }

  return result;
};

/**
 * @returns The number rounded to the nearest integer.
 *
 * (4.5).round(); // => 5
 * (4.4).round(); // => 4
 */
Number.prototype.round = function() {
  return Math.round(this);
};

/**
 * @returns The sign of this number, 0 if the number is 0.
 */
Number.prototype.sign = function() {
  if(this > 0) {
    return 1;
  } else if (this < 0) {
    return -1;
  } else {
    return 0;
  }
};

/**
 * Calls iterator the specified number of times, passing in the number of the
 * current iteration as a parameter: 0 on first call, 1 on the second call, etc.
 *
 * @param {Function} iterator The iterator takes a single parameter, the number
 * of the current iteration.
 * @param {Object} [context] The optional context parameter specifies an object
 * to treat as this in the iterator block.
 *
 * @returns The number of times the iterator was called.
 * @type Number
 */
Number.prototype.times = function(iterator, context) {
  for(var i = 0; i < this; i++) {
    iterator.call(context, i);
  }

  return i;
};

/**
 * Returns the the nearest grid resolution less than or equal to the number.
 *
 *   EX:
 *    (7).snap(8) => 0
 *    (4).snap(8) => 0
 *    (12).snap(8) => 8
 *
 * @param {Number} resolution The grid resolution to snap to.
 * @returns The nearest multiple of resolution lower than the number.
 * @type Number
 */
Number.prototype.snap = function(resolution) {
  return (this / resolution).floor() * resolution;
};

Number.prototype.toColorPart = function() {
  var s = parseInt(this.clamp(0, 255), 10).toString(16);
  if(s.length == 1) {
    s = '0' + s;
  }

  return s;
};

Number.prototype.approach = function(target, maxDelta) {
  return (target - this).clamp(-maxDelta, maxDelta) + this;
};

Number.prototype.approachByRatio = function(target, ratio) {
  return this.approach(target, this * ratio);
};

Number.prototype.approachRotation = function(target, maxDelta) {
  var twoPi = 2 * Math.PI;

  while(target > this + Math.PI) {
    target -= twoPi
  }

  while(target < this - Math.PI) {
    target += twoPi
  }

  return (target - this).clamp(-maxDelta, maxDelta) + this;
};

/**
 * @returns This number constrained between -PI and PI.
 */
Number.prototype.constrainRotation = function() {
  var twoPi = 2 * Math.PI;

  var target = this;

  while(target > Math.PI) {
    target -= twoPi
  }

  while(target < -Math.PI) {
    target += twoPi
  }

  return target;
};

Number.prototype.d = function(sides) {
  var sum = 0;

  this.times(function() {
    sum += rand(sides) + 1;
  });

  return sum;
};

/** The mathematical circle constant of 1 turn. */
Math.TAU = 2 * Math.PI;

<|endoftext|>


<commit_before>function linkStacktrace(oauthToken, stackTrace, userOrRepo) {
    var ret = "";
    var notFound = {};
    var ambiguous = {};
    var cache = {}; // used to cache found files. key: before (see below), value: match.html_url (see further below) or notFound or ambiguous
    stackTrace.split('\n').forEach(function(line) {
        var parsedLine = /(.*)\((.*):(\d*)\)/.exec(line);
        if(parsedLine != null && typeof(parsedLine[1] == "string") && typeof(parsedLine[2] == "string") && typeof(parsedLine[3] == "string")) {
            var before = parsedLine[1];
            var filename = parsedLine[2];
            var linenum = parsedLine[3];
            var compilationUnit = /[a-z]\w*(?:\.[a-z]\w*)*\.[a-zA-Z]\w*/.exec(before)[0];
            if(compilationUnit in cache) {
                if(typeof(cache[compilationUnit]) === "string") {
                    ret += before + "([" + filename + ":" + linenum + "](" + cache[compilationUnit] + "#L" + linenum + "))\n";
                } else {
                    ret += line + '\n';
                }
                return;
            }
            var req = new XMLHttpRequest();
            var userRepo;
            if(userOrRepo.indexOf('/') != -1) // userOrRepo.contains('/')
                userRepo = "repo:" + userOrRepo;
            else
                userRepo = "user:" + userOrRepo;
            req.open("GET", "https://api.github.com/search/code?" +
                "access_token=" + oauthToken + "&" +
                "q=" + filename + "+in:path+" + userRepo + "&" +
                "per_page=2", false);
            req.onload = function(e) {
                if (req.readyState === 4) {
                    if (req.status === 200) {
                        var response = JSON.parse(req.responseText);
                        if(response.total_count === 0) {
                            console.log("file " + filename + " not found");
                            ret += line + '\n';
                            cache[compilationUnit] = notFound;
                            return;
                        } else if(response.total_count > 1) {
                            console.log("file " + filename + " ambiguous");
                            ret += line + '\n';
                            cache[compilationUnit] = ambiguous;
                            return;
                        }
                        // exactly one match
                        var match = response.items[0];
                        if(match.name != filename) {
                            console.log("file name " + match.name + " doesn’t match expected file name " + filename);
                            ret += line + '\n';
                            return;
                        }
                        cache[compilationUnit] = match.html_url;
                        ret += before + "([" + filename + ":" + linenum + "](" + match.html_url + "#L" + linenum + "))\n";
                    } else {
                        console.error(req.statusText);
                        console.error(req.responseText);
                        ret += line + '\n';
                    }
                }
            };
            req.error = function(e) {
                console.error(req.statusText);
                ret += line + '\n';
            };
            req.send(null);
        } else {
            ret += line + '\n';
        }
    });
    return ret;
}
<commit_msg>Append "Generated by linkStacktrace" to bottom<commit_after>function linkStacktrace(oauthToken, stackTrace, userOrRepo) {
    var ret = "";
    var notFound = {};
    var ambiguous = {};
    var cache = {}; // used to cache found files. key: before (see below), value: match.html_url (see further below) or notFound or ambiguous
    stackTrace.split('\n').forEach(function(line) {
        var parsedLine = /(.*)\((.*):(\d*)\)/.exec(line);
        if(parsedLine != null && typeof(parsedLine[1] == "string") && typeof(parsedLine[2] == "string") && typeof(parsedLine[3] == "string")) {
            var before = parsedLine[1];
            var filename = parsedLine[2];
            var linenum = parsedLine[3];
            var compilationUnit = /[a-z]\w*(?:\.[a-z]\w*)*\.[a-zA-Z]\w*/.exec(before)[0];
            if(compilationUnit in cache) {
                if(typeof(cache[compilationUnit]) === "string") {
                    ret += before + "([" + filename + ":" + linenum + "](" + cache[compilationUnit] + "#L" + linenum + "))\n";
                } else {
                    ret += line + '\n';
                }
                return;
            }
            var req = new XMLHttpRequest();
            var userRepo;
            if(userOrRepo.indexOf('/') != -1) // userOrRepo.contains('/')
                userRepo = "repo:" + userOrRepo;
            else
                userRepo = "user:" + userOrRepo;
            req.open("GET", "https://api.github.com/search/code?" +
                "access_token=" + oauthToken + "&" +
                "q=" + filename + "+in:path+" + userRepo + "&" +
                "per_page=2", false);
            req.onload = function(e) {
                if (req.readyState === 4) {
                    if (req.status === 200) {
                        var response = JSON.parse(req.responseText);
                        if(response.total_count === 0) {
                            console.log("file " + filename + " not found");
                            ret += line + '\n';
                            cache[compilationUnit] = notFound;
                            return;
                        } else if(response.total_count > 1) {
                            console.log("file " + filename + " ambiguous");
                            ret += line + '\n';
                            cache[compilationUnit] = ambiguous;
                            return;
                        }
                        // exactly one match
                        var match = response.items[0];
                        if(match.name != filename) {
                            console.log("file name " + match.name + " doesn’t match expected file name " + filename);
                            ret += line + '\n';
                            return;
                        }
                        cache[compilationUnit] = match.html_url;
                        ret += before + "([" + filename + ":" + linenum + "](" + match.html_url + "#L" + linenum + "))\n";
                    } else {
                        console.error(req.statusText);
                        console.error(req.responseText);
                        ret += line + '\n';
                    }
                }
            };
            req.error = function(e) {
                console.error(req.statusText);
                ret += line + '\n';
            };
            req.send(null);
        } else {
            ret += line + '\n';
        }
    });
    ret += "Generated by [linkStacktrace](http://lucaswerkmeister.github.io/linkStacktrace/)\n";
    return ret;
}
<|endoftext|>


<commit_before>// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
importScripts('https://www.gstatic.com/firebasejs/6.0.4/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/6.0.4/firebase-messaging.js');

// Your web app's Firebase configuration
var firebaseConfig = '${FIREBASE_CONFIG}';
// Initialize Firebase
firebase.initializeApp(firebaseConfig);

var messaging = firebase.messaging();

/**
 * Here is is the code snippet to initialize Firebase Messaging in the Service
 * Worker when your app is not hosted on Firebase Hosting.
 // [START initialize_firebase_in_sw]
 // Give the service worker access to Firebase Messaging.
 // Note that you can only use Firebase Messaging here, other Firebase libraries
 // are not available in the service worker.
 importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
 importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');
 // Initialize the Firebase app in the service worker by passing in the
 // messagingSenderId.
 firebase.initializeApp({
   'messagingSenderId': 'YOUR-SENDER-ID'
 });
 // Retrieve an instance of Firebase Messaging so that it can handle background
 // messages.
 const messaging = firebase.messaging();
 // [END initialize_firebase_in_sw]
 **/

// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function(payload) {
    console.log('[firebase-messaging-sw.js] Received background message ', payload);
    // Customize notification here
    var notificationTitle = 'Background Message Title';
    var notificationOptions = {
        body: 'Background Message body.',
        icon: '/firebase-logo.png'
    };

    return self.registration.showNotification(notificationTitle,
        notificationOptions);
});
// [END background_handler]<commit_msg>Updated firebase-messaging-sw.js structure file. Firebase version changed to 7.6.1. #1327<commit_after>// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
importScripts('https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.6.1/firebase-messaging.js');

// Your web app's Firebase configuration
var firebaseConfig = '${FIREBASE_CONFIG}';
// Initialize Firebase
firebase.initializeApp(firebaseConfig);

var messaging = firebase.messaging();

/**
 * Here is is the code snippet to initialize Firebase Messaging in the Service
 * Worker when your app is not hosted on Firebase Hosting.
 // [START initialize_firebase_in_sw]
 // Give the service worker access to Firebase Messaging.
 // Note that you can only use Firebase Messaging here, other Firebase libraries
 // are not available in the service worker.
 importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
 importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');
 // Initialize the Firebase app in the service worker by passing in the
 // messagingSenderId.
 firebase.initializeApp({
   'messagingSenderId': 'YOUR-SENDER-ID'
 });
 // Retrieve an instance of Firebase Messaging so that it can handle background
 // messages.
 const messaging = firebase.messaging();
 // [END initialize_firebase_in_sw]
 **/

// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function(payload) {
    console.log('[firebase-messaging-sw.js] Received background message ', payload);
    // Customize notification here
    var notificationTitle = 'Background Message Title';
    var notificationOptions = {
        body: 'Background Message body.',
        icon: '/firebase-logo.png'
    };

    return self.registration.showNotification(notificationTitle,
        notificationOptions);
});
// [END background_handler]<|endoftext|>


<commit_before>const db = require('./config');
const Sequelize = require('sequelize');

const User = db.define('user', {
  email: { type: Sequelize.TEXT, allowNull: false },
  name: { type: Sequelize.STRING, allowNull: false },
  phone: { type: Sequelize.INTEGER, allowNull: false }
});

const Shop = db.define('shop', {
  address: { type: Sequelize.STRING, allowNull: false },
  email: { type: Sequelize.TEXT, allowNull: false },
  mobile: { type: Sequelize.INTEGER, allowNull: true },
  phone: { type: Sequelize.INTEGER, allowNull: false },
  pickup: { type: Sequelize.BOOLEAN, allowNull: false },
  picture: { type: Sequelize.TEXT, allowNull: true },
})

const Car = db.define('car', {
  make: { type: Sequelize.STRING, allowNull: false },
  model: { type: Sequelize.STRING, allowNull: false },
  nextService: { type: Sequelize.ARRAY(Sequelize.TEXT), allowNull: true },
  picture: { type: Sequelize.TEXT, allowNull: true },
  vin: { type: Sequelize.STRING, allowNull: false },
  year: { type: Sequelize.INTEGER, allowNull: false }
});

const HistoryEntry = db.define('historyentry', {
  date: { type: Sequelize.DATE, allowNull: false },
  description: { type: Sequelize.TEXT, allowNull: false },
  mileage: { type: Sequelize.FLOAT, allowNull: false },
  notes: { type: Sequelize.TEXT, allowNull: true }
});

const Review = db.define('review', {
  rating: { type: Sequelize.INTEGER, allowNull: false },
  response: { type: Sequelize.TEXT, allowNull: true },
  review: { type: Sequelize.TEXT, allowNull: false }
})

const Appointment = db.define('appointment', {
  date: { type: Sequelize.DATE, allowNull: false },
  time: { type: Sequelize.STRING, allowNull: false }
});

const Favorite = db.define('favorite', {});

const Message = db.define('message', {
  from: { type: Sequelize.STRING, allowNull: false },
  message: { type: Sequelize.TEXT, allowNull: false },
  to: { type: Sequelize.STRING, allowNull: false }
});

const Service = db.define('service', {
  service: { type: Sequelize.STRING, allowNull: false }
});

User.hasMany(Car);
Car.belongsTo(User);

User.hasMany(Review);
Review.belongsTo(User);

User.hasMany(Appointment);
Appointment.belongsTo(User);

User.hasMany(Favorite);
Favorite.belongsTo(User);

User.hasMany(Message);
Message.belongsTo(User);

Shop.hasMany(HistoryEntry);
HistoryEntry.belongsTo(Shop);

Shop.hasMany(Review);
Review.hasMany(Shop);

Shop.hasMany(Appointment);
Appointment.belongsTo(Shop);

Shop.hasMany(Favorite);
Favorite.hasMany(Shop);

Shop.hasMany(Message);
Message.belongsTo(Shop);

Car.hasMany(HistoryEntry);
HistoryEntry.belongsTo(Car);

User.sync();
Shop.sync();
Car.sync();
HistoryEntry.sync();
Review.sync();
Appointment.sync();
Favorite.sync();
Message.sync();
Service.sync();

module.exports = {
  User,
  Shop,
  Car,
  HistoryEntry,
  Review,
  Appointment,
  Favorite,
  Message,
  Service
};<commit_msg>Update index.js<commit_after>const db = require('./config');
const Sequelize = require('sequelize');

const User = db.define('user', {
  email: { type: Sequelize.TEXT, allowNull: false },
  name: { type: Sequelize.STRING, allowNull: false },
  phone: { type: Sequelize.INTEGER, allowNull: false }
});

const Shop = db.define('shop', {
  address: { type: Sequelize.STRING, allowNull: false },
  email: { type: Sequelize.TEXT, allowNull: false },
  mobile: { type: Sequelize.BOOLEAN, allowNull: true },
  phone: { type: Sequelize.INTEGER, allowNull: false },
  pickup: { type: Sequelize.BOOLEAN, allowNull: false },
  picture: { type: Sequelize.TEXT, allowNull: true },
})

const Car = db.define('car', {
  make: { type: Sequelize.STRING, allowNull: false },
  model: { type: Sequelize.STRING, allowNull: false },
  nextService: { type: Sequelize.ARRAY(Sequelize.TEXT), allowNull: true },
  picture: { type: Sequelize.TEXT, allowNull: true },
  vin: { type: Sequelize.STRING, allowNull: false },
  year: { type: Sequelize.INTEGER, allowNull: false }
});

const HistoryEntry = db.define('historyentry', {
  date: { type: Sequelize.DATE, allowNull: false },
  description: { type: Sequelize.TEXT, allowNull: false },
  mileage: { type: Sequelize.INTEGER, allowNull: false },
  notes: { type: Sequelize.TEXT, allowNull: true }
});

const Review = db.define('review', {
  rating: { type: Sequelize.INTEGER, allowNull: false },
  response: { type: Sequelize.TEXT, allowNull: true },
  review: { type: Sequelize.TEXT, allowNull: false }
})

const Appointment = db.define('appointment', {
  date: { type: Sequelize.DATE, allowNull: false },
  time: { type: Sequelize.STRING, allowNull: false }
});

const Favorite = db.define('favorite', {});

const Message = db.define('message', {
  from: { type: Sequelize.STRING, allowNull: false },
  message: { type: Sequelize.TEXT, allowNull: false }
});

const Service = db.define('service', {
  service: { type: Sequelize.STRING, allowNull: false }
});

User.hasMany(Car);
Car.belongsTo(User);

User.hasMany(Review);
Review.belongsTo(User);

User.hasMany(Appointment);
Appointment.belongsTo(User);

User.hasMany(Favorite);
Favorite.belongsTo(User);

User.hasMany(Message);
Message.belongsTo(User);

Shop.hasMany(HistoryEntry);
HistoryEntry.belongsTo(Shop);

Shop.hasMany(Review);
Review.hasMany(Shop);

Shop.hasMany(Appointment);
Appointment.belongsTo(Shop);

Shop.hasMany(Favorite);
Favorite.hasMany(Shop);

Shop.hasMany(Message);
Message.belongsTo(Shop);

Car.hasMany(HistoryEntry);
HistoryEntry.belongsTo(Car);

User.sync();
Shop.sync();
Car.sync();
HistoryEntry.sync();
Review.sync();
Appointment.sync();
Favorite.sync();
Message.sync();
Service.sync();

module.exports = {
  User,
  Shop,
  Car,
  HistoryEntry,
  Review,
  Appointment,
  Favorite,
  Message,
  Service
};
<|endoftext|>


<commit_before>'use strict';
var chokidar = require('chokidar');
const readChunk = require('read-chunk'); // npm install read-chunk
const fileType = require('file-type');
const mv = require('mv');
const path_object = require('path');
const exec = require('child_process').exec;
const rmdir = require('rmdir');

var fs = require('fs');
var Buffer = require('buffer').Buffer;

var log = console.log.bind(console);

var config = {};
  config['watched_folder'] = '/Users/dsteplight/Downloads';

  //documents
  config['pdf_folder'] = '/Users/dsteplight/Documents/PDF';
  config['xlsx_folder'] = '/Users/dsteplight/Documents/EXCEL';
  config['php_folder'] = '/Users/dsteplight/Documents/PHP';
  config['json_folder'] = '/Users/dsteplight/Documents/JSON';
  config['md_folder'] = '/Users/dsteplight/Documents/MD';

  //todo: make these go into the pictures folder

  //picture files
  config['jpeg_folder'] = '/Users/dsteplight/Documents/JPEG';
  config['gif_folder'] = '/Users/dsteplight/Documents/GIF';
  config['png_folder'] = '/Users/dsteplight/Documents/PNG';

  //video files
  config['wmv_folder'] = '/Users/dsteplight/Movies/WMV';
  config['mp4_folder'] = '/Users/dsteplight/Movies/MP4';
  config['flv_folder'] = '/Users/dsteplight/Movies/FLV';
  config['avi_folder'] = '/Users/dsteplight/Movies/AVI';
  config['webm_folder'] = '/Users/dsteplight/Movies/WEBM';
  config['mkv_folder'] = '/Users/dsteplight/Movies/MKV';
  config['ts_folder'] = '/Users/dsteplight/Movies/TS';
  config['threegp_folder'] = '/Users/dsteplight/Movies/3PG';
  config['mpg_folder'] = '/Users/dsteplight/Movies/MPG';
  config['mov_folder'] = '/Users/dsteplight/Movies/MOV';

  //music files
  config['mp3_folder'] = '/Users/dsteplight/Music/MP3';
  config['aac_folder'] = '/Users/dsteplight/Music/AAC';

  //apps
  config['screen_saver_folder'] = '/Users/dsteplight/Documents/SCREEN-SAVER';
  config['dmg_folder'] = '/Users/dsteplight/DMG';

   //calendars
  config['ics_folder'] = '/Users/dsteplight/ICS';
  config['ical_folder'] = '/Users/dsteplight/ICAL';

  //compressed files
  config['zip_folder'] = '/Users/dsteplight/ZIP';
  config['gz_folder'] = '/Users/dsteplight/GZ';
  config['tar_folder'] = '/Users/dsteplight/TAR';
  config['phar_folder'] = '/Users/dsteplight/PHAR';
  config['bz2_folder'] = '/Users/dsteplight/BZ2';

  //database files (todo: include .sql.gz files in SQL folder)
  config['sql_folder'] = '/Users/dsteplight/SQL';
  config['sqlite_folder'] = '/Users/dsteplight/SQLITE';

for (var key in config)
{
   //make sure there is a path set and skipped over the watch folder
   if ((typeof config[key]) != 'undefined' && key !== 'watched_folder')
   {
       let path_string = config[key];
      if (!fs.existsSync(path_string))
      {
         fs.mkdirSync(path_string);
         console.log('A NEW FOLDER HAS BEEN CREATED: '+path_string);
      }
   }
}

var watcher = chokidar.watch(config.watched_folder, {
  ignored: /[\/\\]\./,
  interval: 10000,
  persistent: true,
  depth: 0
});

function moveFile( folder, file, path_of_current_file)
{
         let targeted_file = folder+path_object.sep+file;

         mv(path_of_current_file, targeted_file, function(err)
         {
             log('This file has been moved to ', targeted_file);
         });

return true;
}

watcher
  .on('add', function(path) {

      const file_name = path_object.basename(path);
      const file_type = path_object.extname(path);

         if( file_type !== null )
         {
            switch(file_type)
            {
               case '.jpg':
               case '.jpeg':
                     var target_folder = config.jpeg_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.gif':
                     var target_folder = config.gif_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.png':
                     var target_folder = config.png_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.pdf':
                     var target_folder = config.pdf_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.wmv':
                     var target_folder = config.wmv_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mp4':
                     var target_folder = config.mp4_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.flv':
                     var target_folder = config.flv_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.avi':
                     var target_folder = config.avi_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mp3':
                     var target_folder = config.mp3_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.webm':
                     var target_folder = config.webm_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mkv':
                     var target_folder = config.mkv_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.ts':
                     var target_folder = config.ts_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.3gp':
                     var target_folder = config.threegp_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mpg':
                     var target_folder = config.mpg_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.aac':
                     var target_folder = config.aac_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.gz':
                     var target_folder = config.gz_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.tar':
                     var target_folder = config.tar_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.phar':
                     var target_folder = config.phar_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.bz2':
                     var target_folder = config.bz2_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.sql':
                     var target_folder = config.sql_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.sqlite':
                     var target_folder = config.sqlite_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.dmg':
                     var target_folder = config.dmg_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.ics':
                     var target_folder = config.ics_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.ical':
                     var target_folder = config.ical_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.xls':
               case '.xlsx':
                     var target_folder = config.xlsx_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.php':
                     var target_folder = config.php_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.json':
                     var target_folder = config.json_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.md':
                     var target_folder = config.md_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mov':
                     var target_folder = config.mov_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.zip':
                     var target_folder = config.zip_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.saver':
                     var target_folder = config.screen_saver_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               default:
                  break;
            }
         }

   })
  .on('addDir', function(path) {

      let directory_name = path_object.basename(path);
      let file_name = directory_name+".zip";
      let target_folder = config.zip_folder;
      let targeted_file = target_folder+path_object.sep+file_name;

         fs.stat(path, function (err, stats)
         {

           if (err)
           {
             //this should always exsits since we are only dealing with directories right now
           }

           //make sure not to also zip up the actual folder that is being watched
           if (stats.isDirectory() && path !== config.watched_folder )
           {
            let child = exec("zip -r "+directory_name+".zip "+path,{maxBuffer: 1024 * 1000}, function (error, stdout, stderr)
                        {
                             if (error !== null)
                             {
                               console.log('exec error: ' + error);
                             }
                             else
                             {
                                 console.log("THERE SHOULD BE A NEW ZIP FOLDER "+directory_name+".zip");

                                 //the newly created zip folder will exist in the same folder where this script is running and not in the config.watched_folder path
                                 mv(file_name, targeted_file, function(err)
                                 {
                                     log('This zippped file has been moved to ', targeted_file);
                                     //now delete the folder from the config.watched_files directory
                                     rmdir(path, function (err, dirs, files)
                                     {
                                      console.log(dirs);
                                      console.log(files);
                                      console.log('all files are removed from '+ path);
                                     });

                                 });
                              }
                        });

           }

         });

  })
  .on('change', function(path) {

})
  .on('unlink', function(path) {
  })
  .on('unlinkDir', function(path) {
  })
  .on('error', function(error) {
         console.log('DEBUG: The script caught an error', error);
  })
  .on('ready', function() { log('Initial scan complete. Ready for changes.'); })

<commit_msg>added support for tgz files<commit_after>'use strict';
var chokidar = require('chokidar');
const readChunk = require('read-chunk'); // npm install read-chunk
const fileType = require('file-type');
const mv = require('mv');
const path_object = require('path');
const exec = require('child_process').exec;
const rmdir = require('rmdir');

var fs = require('fs');
var Buffer = require('buffer').Buffer;

var log = console.log.bind(console);

var config = {};
  config['watched_folder'] = '/Users/dsteplight/Downloads';

  //documents
  config['pdf_folder'] = '/Users/dsteplight/Documents/PDF';
  config['xlsx_folder'] = '/Users/dsteplight/Documents/EXCEL';
  config['php_folder'] = '/Users/dsteplight/Documents/PHP';
  config['json_folder'] = '/Users/dsteplight/Documents/JSON';
  config['md_folder'] = '/Users/dsteplight/Documents/MD';

  //todo: make these go into the pictures folder

  //picture files
  config['jpeg_folder'] = '/Users/dsteplight/Documents/JPEG';
  config['gif_folder'] = '/Users/dsteplight/Documents/GIF';
  config['png_folder'] = '/Users/dsteplight/Documents/PNG';

  //video files
  config['wmv_folder'] = '/Users/dsteplight/Movies/WMV';
  config['mp4_folder'] = '/Users/dsteplight/Movies/MP4';
  config['flv_folder'] = '/Users/dsteplight/Movies/FLV';
  config['avi_folder'] = '/Users/dsteplight/Movies/AVI';
  config['webm_folder'] = '/Users/dsteplight/Movies/WEBM';
  config['mkv_folder'] = '/Users/dsteplight/Movies/MKV';
  config['ts_folder'] = '/Users/dsteplight/Movies/TS';
  config['threegp_folder'] = '/Users/dsteplight/Movies/3PG';
  config['mpg_folder'] = '/Users/dsteplight/Movies/MPG';
  config['mov_folder'] = '/Users/dsteplight/Movies/MOV';

  //music files
  config['mp3_folder'] = '/Users/dsteplight/Music/MP3';
  config['aac_folder'] = '/Users/dsteplight/Music/AAC';

  //apps
  config['screen_saver_folder'] = '/Users/dsteplight/Documents/SCREEN-SAVER';
  config['dmg_folder'] = '/Users/dsteplight/DMG';

   //calendars
  config['ics_folder'] = '/Users/dsteplight/ICS';
  config['ical_folder'] = '/Users/dsteplight/ICAL';

  //compressed files
  config['zip_folder'] = '/Users/dsteplight/ZIP';
  config['gz_folder'] = '/Users/dsteplight/GZ';
  config['tar_folder'] = '/Users/dsteplight/TAR';
  config['phar_folder'] = '/Users/dsteplight/PHAR';
  config['bz2_folder'] = '/Users/dsteplight/BZ2';
  config['tgz_folder'] = '/Users/dsteplight/TGZ';

  //database files (todo: include .sql.gz files in SQL folder)
  config['sql_folder'] = '/Users/dsteplight/SQL';
  config['sqlite_folder'] = '/Users/dsteplight/SQLITE';

for (var key in config)
{
   //make sure there is a path set and skipped over the watch folder
   if ((typeof config[key]) != 'undefined' && key !== 'watched_folder')
   {
       let path_string = config[key];
      if (!fs.existsSync(path_string))
      {
         fs.mkdirSync(path_string);
         console.log('A NEW FOLDER HAS BEEN CREATED: '+path_string);
      }
   }
}

var watcher = chokidar.watch(config.watched_folder, {
  ignored: /[\/\\]\./,
  interval: 10000,
  persistent: true,
  depth: 0
});

function moveFile( folder, file, path_of_current_file)
{
         let targeted_file = folder+path_object.sep+file;

         mv(path_of_current_file, targeted_file, function(err)
         {
             log('This file has been moved to ', targeted_file);
         });

return true;
}

watcher
  .on('add', function(path) {

      const file_name = path_object.basename(path);
      const file_type = path_object.extname(path);

         if( file_type !== null )
         {
            switch(file_type)
            {
               case '.jpg':
               case '.jpeg':
                     var target_folder = config.jpeg_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.gif':
                     var target_folder = config.gif_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.png':
                     var target_folder = config.png_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.pdf':
                     var target_folder = config.pdf_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.wmv':
                     var target_folder = config.wmv_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mp4':
                     var target_folder = config.mp4_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.flv':
                     var target_folder = config.flv_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.avi':
                     var target_folder = config.avi_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mp3':
                     var target_folder = config.mp3_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.webm':
                     var target_folder = config.webm_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mkv':
                     var target_folder = config.mkv_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.ts':
                     var target_folder = config.ts_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.3gp':
                     var target_folder = config.threegp_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mpg':
                     var target_folder = config.mpg_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.aac':
                     var target_folder = config.aac_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.gz':
                     var target_folder = config.gz_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.tar':
                     var target_folder = config.tar_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.phar':
                     var target_folder = config.phar_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.bz2':
                     var target_folder = config.bz2_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.sql':
                     var target_folder = config.sql_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.sqlite':
                     var target_folder = config.sqlite_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.dmg':
                     var target_folder = config.dmg_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.ics':
                     var target_folder = config.ics_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.ical':
                     var target_folder = config.ical_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.xls':
               case '.xlsx':
                     var target_folder = config.xlsx_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.php':
                     var target_folder = config.php_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.json':
                     var target_folder = config.json_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.md':
                     var target_folder = config.md_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.mov':
                     var target_folder = config.mov_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.zip':
                     var target_folder = config.zip_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.tgz':
                     var target_folder = config.tgz_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               case '.saver':
                     var target_folder = config.screen_saver_folder;
                     moveFile( target_folder, file_name, path );
                  break;
               default:
                  break;
            }
         }

   })
  .on('addDir', function(path) {

      let directory_name = path_object.basename(path);
      let file_name = directory_name+".zip";
      let target_folder = config.zip_folder;
      let targeted_file = target_folder+path_object.sep+file_name;

         fs.stat(path, function (err, stats)
         {

           if (err)
           {
             //this should always exsits since we are only dealing with directories right now
           }

           //make sure not to also zip up the actual folder that is being watched
           if (stats.isDirectory() && path !== config.watched_folder )
           {
            let child = exec("zip -r "+directory_name+".zip "+path,{maxBuffer: 1024 * 1000}, function (error, stdout, stderr)
                        {
                             if (error !== null)
                             {
                               console.log('exec error: ' + error);
                             }
                             else
                             {
                                 console.log("THERE SHOULD BE A NEW ZIP FOLDER "+directory_name+".zip");

                                 //the newly created zip folder will exist in the same folder where this script is running and not in the config.watched_folder path
                                 mv(file_name, targeted_file, function(err)
                                 {
                                     log('This zippped file has been moved to ', targeted_file);
                                     //now delete the folder from the config.watched_files directory
                                     rmdir(path, function (err, dirs, files)
                                     {
                                      console.log(dirs);
                                      console.log(files);
                                      console.log('all files are removed from '+ path);
                                     });

                                 });
                              }
                        });

           }

         });

  })
  .on('change', function(path) {

})
  .on('unlink', function(path) {
  })
  .on('unlinkDir', function(path) {
  })
  .on('error', function(error) {
         console.log('DEBUG: The script caught an error', error);
  })
  .on('ready', function() { log('Initial scan complete. Ready for changes.'); })

<|endoftext|>