<commit_before>import ui.View as View;
import ui.ScrollView as ScrollView;
import ui.GestureView as GestureView;

import .AdventureMapBackgroundView;
import .AdventureMapPathsView;
import .AdventureMapNodesView;
import .AdventureMapDoodadsView;

exports = Class(ScrollView, function (supr) {
	this.init = function (opts) {
		this._tileWidth = opts.tileSettings.tileWidth;
		this._tileHeight = opts.tileSettings.tileHeight;

		this._totalWidth = opts.gridSettings.width * this._tileWidth;
		this._totalHeight = opts.gridSettings.height * this._tileHeight;

		var scale = opts.scale || 0.5;

		opts = merge(
			opts,
			{
				scrollX: true,
				scrollY: true,
				scrollBounds: {
					minX: 0,
					minY: 0,
					maxX: this._totalWidth * scale,
					maxY: this._totalHeight * scale
				},
				bounce: false
			}
		);

		supr(this, 'init', [opts]);

		this._tileSettings = opts.tileSettings;
		this._adventureMapLayers = [];
		this._inputLayerIndex = opts.inputLayerIndex;

		this._content = new GestureView({
			superview: this,
			x: 0,
			y: 0,
			width: this._totalWidth,
			height: this._totalHeight,
			scale: scale
		});

		var ctors = [
				AdventureMapBackgroundView,
				AdventureMapPathsView,
				AdventureMapNodesView,
				AdventureMapDoodadsView
			];
		for (var i = 0; i < ctors.length; i++) {
			this._adventureMapLayers.push(new ctors[i]({
				superview: this._content,
				adventureMapView: this,
				x: 0,
				y: 0,
				width: this._totalWidth,
				height: this._totalHeight,
				map: opts.map,
				gridSettings: opts.gridSettings,
				tileCtor: ctors[i],
				tileSettings: opts.tileSettings,
				gridSettings: opts.gridSettings,
				nodeSettings: opts.nodeSettings,
				pathSettings: opts.pathSettings,
				editMode: opts.editMode,
				blockEvents: opts.editMode ? (i !== 0) : (i < 2)
			}));
		}
	};

	this.onUpdate = function (data) {
		for (var i = 0; i < 4; i++) {
			var adventureMapLayer = this._adventureMapLayers[i];
			adventureMapLayer && adventureMapLayer.onUpdate && adventureMapLayer.onUpdate(data);
		}
	};

	this.getAdventureMapLayers = function () {
		return this._adventureMapLayers;
	};

	this.setScale = function (scale) {
		this._content.style.scale = scale;

		this.setScrollBounds({
			minX: 0,
			minY: 0,
			maxX: this._totalWidth * scale,
			maxY: this._totalHeight * scale
		});
		this.scrollTo(offsetX, offsetY, 0);
	};

	this.refreshTile = function (tileX, tileY) {
		var adventureMapLayers = this._adventureMapLayers;
		var i = this._adventureMapLayers.length;

		while (i) {
			this._adventureMapLayers[--i].refreshTile(tileX, tileY);
		}
	};

	this.focusNodeById = function (node) {
		var scale = this._content.style.scale;
		var x = Math.max((node.tileX * this._tileSettings.tileWidth) * scale - this.style.width * 0.5, 0);
		var y = Math.max((node.tileY * this._tileSettings.tileHeight) * scale - this.style.height * 0.5, 0);

		this.scrollTo(x, y, 300);
	};

	this.removeItemViews = function () {
		this._adventureMapLayers[3].removeItemViews();
	};
});<commit_msg>Fixed pinch zoom<commit_after>import ui.View as View;
import ui.ScrollView as ScrollView;
import ui.GestureView as GestureView;

import .AdventureMapBackgroundView;
import .AdventureMapPathsView;
import .AdventureMapNodesView;
import .AdventureMapDoodadsView;

exports = Class(ScrollView, function (supr) {
	this.init = function (opts) {
		this._tileWidth = opts.tileSettings.tileWidth;
		this._tileHeight = opts.tileSettings.tileHeight;

		this._totalWidth = opts.gridSettings.width * this._tileWidth;
		this._totalHeight = opts.gridSettings.height * this._tileHeight;

		var scale = opts.scale || 0.5;

		opts = merge(
			opts,
			{
				scrollX: true,
				scrollY: true,
				scrollBounds: {
					minX: 0,
					minY: 0,
					maxX: this._totalWidth * scale,
					maxY: this._totalHeight * scale
				},
				bounce: false
			}
		);

		supr(this, 'init', [opts]);

		this._tileSettings = opts.tileSettings;
		this._adventureMapLayers = [];
		this._inputLayerIndex = opts.inputLayerIndex;

		this._content = new GestureView({
			superview: this,
			x: 0,
			y: 0,
			width: this._totalWidth,
			height: this._totalHeight,
			scale: scale
		});

		this._pinchEndTimeout = null;
		this._pinch = false;

		this._content.on('FingerUp', bind(this, 'onFingerUp'));
		this._content.on('ClearMulti', bind(this, 'onClearMulti'));
		this._content.on('Pinch', bind(this, 'onPinch'));

		var ctors = [
				AdventureMapBackgroundView,
				AdventureMapPathsView,
				AdventureMapNodesView,
				AdventureMapDoodadsView
			];
		for (var i = 0; i < ctors.length; i++) {
			this._adventureMapLayers.push(new ctors[i]({
				superview: this._content,
				adventureMapView: this,
				x: 0,
				y: 0,
				width: this._totalWidth,
				height: this._totalHeight,
				map: opts.map,
				gridSettings: opts.gridSettings,
				tileCtor: ctors[i],
				tileSettings: opts.tileSettings,
				gridSettings: opts.gridSettings,
				nodeSettings: opts.nodeSettings,
				pathSettings: opts.pathSettings,
				editMode: opts.editMode,
				blockEvents: opts.editMode ? (i !== 0) : (i < 2)
			}));
		}
	};

	this.onUpdate = function (data) {
		for (var i = 0; i < 4; i++) {
			var adventureMapLayer = this._adventureMapLayers[i];
			adventureMapLayer && adventureMapLayer.onUpdate && adventureMapLayer.onUpdate(data);
		}
	};

	this.onFingerUp = function (activeFingers) {
		activeFingers || this.onClearMulti();
	};

	this.onClearMulti = function () {
		this._pinchEndTimeout && clearTimeout(this._pinchEndTimeout);
		this._pinchEndTimeout = setTimeout(
			bind(this, function () {
				this._pinch = false;
			}),
			10
		);
	};

	this.onPinch = function (pinchScale) {
		this._pinch = true;

		this.setScale(pinchScale);
	};

	this.onDrag = function (dragEvt, moveEvt, delta) {
		this._pinch || supr(this, 'onDrag', arguments);
	};

	this.onDragStop = function (dragEvt, selectEvt) {
		this._pinch || supr(this, 'onDragStop', arguments);
	};

	this.getAdventureMapLayers = function () {
		return this._adventureMapLayers;
	};

	this.getTileWidth = function () {
		return this._tileWidth;
	};

	this.getTileHeight = function () {
		return this._tileHeight;
	};

	this.getScale = function (scale) {
		return this._content.style.scale;
	};

	this.setScale = function (scale) {
		var lastScale = this._content.style.scale;

		this._content.style.scale = scale;

		var x = this._contentView.style.x * scale / lastScale + (lastScale - scale) * this.style.width * 0.5;
		var y = this._contentView.style.y * scale / lastScale + (lastScale - scale) * this.style.height * 0.5;

		this._contentView.style.x = Math.min(Math.max(x, -(this._totalWidth * scale - this.style.width)), 0);
		this._contentView.style.y = Math.min(Math.max(y, -(this._totalHeight * scale - this.style.height)), 0);

		this.setScrollBounds({
			minX: 0,
			minY: 0,
			maxX: this._totalWidth * scale,
			maxY: this._totalHeight * scale
		});
	};

	this.refreshTile = function (tileX, tileY) {
		var adventureMapLayers = this._adventureMapLayers;
		var i = this._adventureMapLayers.length;

		while (i) {
			this._adventureMapLayers[--i].refreshTile(tileX, tileY);
		}
	};

	this.focusNodeById = function (node) {
		var scale = this._content.style.scale;
		var x = Math.max((node.tileX * this._tileSettings.tileWidth) * scale - this.style.width * 0.5, 0);
		var y = Math.max((node.tileY * this._tileSettings.tileHeight) * scale - this.style.height * 0.5, 0);

		this.scrollTo(x, y, 300);
	};

	this.removeItemViews = function () {
		this._adventureMapLayers[3].removeItemViews();
	};
});<|endoftext|>


<commit_before>(function(){

    angular.module('listModule', [])
        .component('myList', { // the tag for using this is <my-example>
            templateUrl: "../angular/templates/list-template.html",
            controller: listController
        });

    function listController() {
        // do stuff here
        var self = this;
        // get object from nav, create and store items in object.
        function addItem() {

        }
        function removeItem() {

        }
        function updateItem() {

        }
        function expandItem() {

        }
		function showItems() {

		}

    }

}());
<commit_msg>controller functions<commit_after>(function(){

    angular.module('listModule', [navModule])
        .component('myList', { // the tag for using this is <my-example>
            templateUrl: "../angular/templates/list-template.html",
            controller: listController
        });

    function listController() {
        // do stuff here
        var self = this;
        // get object from nav, create and store items in object.
        function addItem() {

        }
        function removeItem() {

        }
        function updateItem() {

        }
        function expandItem() {

        }
		function showItems() {

		}

    }

}());
<|endoftext|>


<commit_before>  /*Application global vars*/
  var year            = 2003;
  var compare         = 'ninguna';
  var state           = "mapa";

  $(document).ready(function(){

    //Deep linking management
    var route = window.location.hash.replace('#','').split('/');
    goToHash(route);
  });

  function initializeApp() {
    initializeWelcomeWindow(); // initialize welcomewindow
    initializeGraph();         // initialize graph
    initializeHeader();        // initialize header options (selects and more) and graph/map menu
    initializeMap();           // initialize map and map modules
    initializeKeyBindings();   // initialize key bindings
  }

  function initializeKeyBindings() {
    $(document).keyup(function(e) {
      if (e.keyCode == 27) {
        $("#welcomewindow").fadeOut();
        explanationwindow.hide();
        comparewindow.hide();
        if (state == "grafico") {
          graphBubbleInfowindow.hide();
        } else {
          infowindow.hide();
        }
      }
    });
  }

  function goToHash(route) {
    //Check variable application state
    if (route[0]!="mapa" && route[0]!="grafico") {
      initializeApp();
      return false;
    }

    // //Check variable of compare
    if (!$('div.option_list ul li a.'+route[5]).length) {
      initializeApp();
      return false;
    }

    // Check length of the array
    if (route.length!=8) {
      initializeApp();
      return false;
    }

    // Check provincia-autonomia-municipio
    if (route[6]!="autonomias" && route[6]!="municipios" && route[6]!="provincias") {
      initializeApp();
      return false;
    }

    //check 1-num 2-num 3-num 4-year 5-string
    if (isNaN(route[1]) || isNaN(route[2]) || isNaN(route[3]) || isNaN(route[4]) || route[4].length!=4 || !isNaN(route[5])) {
      initializeApp();
      return false;
    }

    // Check zoom
    if (route[3]>12 || route[3]<6) {
      initializeApp();
      return false;
    }

    //All ok - change variables!
    start_center = new google.maps.LatLng(parseFloat(route[1]),parseFloat(route[2]));
    start_zoom = parseInt(route[3]);
    year = parseInt(route[4]);
    state = route[0];
    $("div.year_slider").slider("value",year);
    compare = route[5].replace(/_/g," ");
    deep = route[6];
    name = route[7];
    var value = $('div.option_list ul li a.'+route[5]).text();
    // Remove selected variable
    $('div.option_list ul li.selected').each(function(i,ele){$(ele).removeClass('selected');});
    $('div.select').each(function(i,ele){$(ele).removeClass('selected');});
    $('div.select span.inner_select a').each(function(i,ele){var text = $(ele).attr('title'); $(ele).text(text);});
    // Add new selected variable
    $('div.option_list ul li a.'+route[5]).closest('li').addClass('selected');
    $('div.option_list ul li a.'+route[5]).closest('div.select').addClass('selected');
    $('div.option_list ul li a.'+route[5]).closest('div.select').find('span.inner_select a').text(value);

    initializeApp();
  }

  function initializeWelcomeWindow() {
    $("#welcomewindow a.close_info").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow");
    });

    $("#welcomewindow a.start").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow");

        var text = $("div.select div.option_list ul li a.paro").text();
        $("div.select div.outer_select.money").parent().addClass("selected");
        $("div.select div.outer_select.money span.inner_select a").text(text);
        $("div.select div.option_list ul li a.paro").parent().addClass("selected");
        compare = "paro";

        if (year < 2005) { // because we don't have paro information prior 2005
          updateNewSliderValue(2005, year);
        }
        changeHash();
    });

    $("#welcomewindow a.explore").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow");
    });

    $("#welcomewindow a.more_info").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow", function() {
        explanationwindow.show();
      });
    });

    $("#welcomewindow a.know_more").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow");
    });
  }

  function changeHash() {
    var latlng = peninsula.getCenter();
    var zoom = peninsula.getZoom();
    var hash = "#"+ state + "/" + latlng.lat().toFixed(3)+"/"+latlng.lng().toFixed(3)+"/"+zoom+"/"+year+"/"+compare.replace(/ /g,'_')+"/"+deep+"/"+name;
    window.location.hash = hash;
    updateWadusText();
  }
<commit_msg>Added binding to body so we can close the welcomewindow on clicking outside<commit_after>  /*Application global vars*/
  var year            = 2003;
  var compare         = 'ninguna';
  var state           = "mapa";

  $(document).ready(function(){

    //Deep linking management
    var route = window.location.hash.replace('#','').split('/');
    goToHash(route);
  });

  function initializeApp() {
    initializeWelcomeWindow(); // initialize welcomewindow
    initializeGraph();         // initialize graph
    initializeHeader();        // initialize header options (selects and more) and graph/map menu
    initializeMap();           // initialize map and map modules
    initializeKeyBindings();   // initialize key bindings
  }

  function initializeKeyBindings() {
    $(document).keyup(function(e) {
      if (e.keyCode == 27) {
        $("#welcomewindow").fadeOut();
        explanationwindow.hide();
        comparewindow.hide();
        if (state == "grafico") {
          graphBubbleInfowindow.hide();
        } else {
          infowindow.hide();
        }
      }
    });
  }

  function goToHash(route) {
    //Check variable application state
    if (route[0]!="mapa" && route[0]!="grafico") {
      initializeApp();
      return false;
    }

    // //Check variable of compare
    if (!$('div.option_list ul li a.'+route[5]).length) {
      initializeApp();
      return false;
    }

    // Check length of the array
    if (route.length!=8) {
      initializeApp();
      return false;
    }

    // Check provincia-autonomia-municipio
    if (route[6]!="autonomias" && route[6]!="municipios" && route[6]!="provincias") {
      initializeApp();
      return false;
    }

    //check 1-num 2-num 3-num 4-year 5-string
    if (isNaN(route[1]) || isNaN(route[2]) || isNaN(route[3]) || isNaN(route[4]) || route[4].length!=4 || !isNaN(route[5])) {
      initializeApp();
      return false;
    }

    // Check zoom
    if (route[3]>12 || route[3]<6) {
      initializeApp();
      return false;
    }

    //All ok - change variables!
    start_center = new google.maps.LatLng(parseFloat(route[1]),parseFloat(route[2]));
    start_zoom = parseInt(route[3]);
    year = parseInt(route[4]);
    state = route[0];
    $("div.year_slider").slider("value",year);
    compare = route[5].replace(/_/g," ");
    deep = route[6];
    name = route[7];
    var value = $('div.option_list ul li a.'+route[5]).text();
    // Remove selected variable
    $('div.option_list ul li.selected').each(function(i,ele){$(ele).removeClass('selected');});
    $('div.select').each(function(i,ele){$(ele).removeClass('selected');});
    $('div.select span.inner_select a').each(function(i,ele){var text = $(ele).attr('title'); $(ele).text(text);});
    // Add new selected variable
    $('div.option_list ul li a.'+route[5]).closest('li').addClass('selected');
    $('div.option_list ul li a.'+route[5]).closest('div.select').addClass('selected');
    $('div.option_list ul li a.'+route[5]).closest('div.select').find('span.inner_select a').text(value);

    initializeApp();
  }

  function initializeWelcomeWindow() {

    $('body').click(function(event) {
      if (!$(event.target).closest('div#welcomewindow').length) {
        $("#welcomewindow").fadeOut("slow");
        $('body').unbind('click');
      }
    });

    $("#welcomewindow a.close_info").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow");
    });

    $("#welcomewindow a.start").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();

      $("#welcomewindow").fadeOut("slow");

        var text = $("div.select div.option_list ul li a.paro").text();
        $("div.select div.outer_select.money").parent().addClass("selected");
        $("div.select div.outer_select.money span.inner_select a").text(text);
        $("div.select div.option_list ul li a.paro").parent().addClass("selected");
        compare = "paro";

        if (year < 2005) { // because we don't have paro information prior 2005
          updateNewSliderValue(2005, year);
        }
        changeHash();
    });

    $("#welcomewindow a.explore").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow");
    });

    $("#welcomewindow a.more_info").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow", function() {
        explanationwindow.show();
      });
    });

    $("#welcomewindow a.know_more").click(function(ev) {
      ev.stopPropagation();
      ev.preventDefault();
      $("#welcomewindow").fadeOut("slow");
    });
  }

  function changeHash() {
    var latlng = peninsula.getCenter();
    var zoom = peninsula.getZoom();
    var hash = "#"+ state + "/" + latlng.lat().toFixed(3)+"/"+latlng.lng().toFixed(3)+"/"+zoom+"/"+year+"/"+compare.replace(/ /g,'_')+"/"+deep+"/"+name;
    window.location.hash = hash;
    updateWadusText();
  }
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict'

const execa = require('execa')
const fs = require('fs-extra')
const getStream = require('get-stream')
const globby = require('globby')
const ini = require('ini')
const path = require('path')
const pify = require('pify')

const unary = (func) => (a) => func(a)
const binary = (func) => (a, b) => func(a, b)

const exists = unary(pify(fs.exists))
const move = binary(pify(fs.move))
const read = binary(pify(fs.readFile))
const remove = unary(pify(fs.remove))
const write = binary(pify(fs.writeFile))

const FFMPEG_PATH = '/opt/bin/ffmpeg'
const FFPROBE_PATH = '/opt/bin/ffprobe'

const COUCH_COMPLETE_PATH = '/share/CACHEDEV1_DATA/Multimedia/Movies'
const SONARR_COMPLETE_PATH = '/share/CACHEDEV1_DATA/Multimedia/TV'
const WATCH_PATH = '/share/CACHEDEV1_DATA/Download/complete'

const MANUAL_SCRIPT_PATH = '/share/CACHEDEV1_DATA/scripts/manual.py'
const SAB_SCRIPT_PATH = '/share/CACHEDEV1_DATA/scripts/SABPostProcess.py'

const confpath1 = path.join(__dirname, 'autoProcess.1.ini')
const confpath2 = path.join(__dirname, 'autoProcess.ini')
const config2 = read(confpath2, 'utf8').then(ini.parse)

const argv = process.argv.slice(2)
const inpath = path.resolve(argv[0])
const foldername = path.basename(inpath)
const imdbid = (/\btt\d{7,}/.exec(foldername) || [''])[0]

if (argv.length < 3) {
  argv.length = 7
  // The name of the job name without path or file extension.
  argv[2] = foldername
  // The indexer's report number. It's not used by SABPostProcess.py.
  argv[3] = 0
  // The user-defined category used to signal which manager is notified.
  argv[4] = argv[1] || inpath.toLowerCase().split(path.sep).includes('tv') ? 'tv' : 'movies'
  // The group the NZB was posted in, e.g. alt.binaries.x. We use it to detect a "Manual Run".
  argv[5] = 'Manual Run'
  // The status of post processing (0 = OK, 1=failed verification, 2=failed unpack, 3=1+2).
  argv[6] = 0
  // The name of the NZB file used by SABPostProcess.py to detect a "Manual Run".
  argv[1] = `${ foldername }.nzb`
}
const category = argv[4]
const group = argv[5]
const outpath = path.join(WATCH_PATH, category, foldername)

const langMap = new Map(Object.entries({
  'eng': 'en',
  'und': 'en'
}))

const rankMap = new Map(Object.entries({
  'mono': 1,
  'stereo': 2,
  'downmix': 3,
  '2.1': 4,
  '3.0(back)': 5,
  '3.0': 6,
  '3.1': 7,
  'quad(side)': 8,
  'quad': 9,
  '4.0': 10,
  '4.1': 11,
  '5.0(side)': 12,
  '5.0': 13,
  '5.1(side)': 14,
  '5.1': 15,
  'hexagonal': 16,
  '6.0(front)':  17,
  '6.0': 18,
  '6.1(back)': 19,
  '6.1(front)': 20,
  '6.1': 21,
  '7.0(front)': 22,
  '7.0': 23,
  'octagonal': 24,
  '7.1(wide-side)': 25,
  '7.1(wide)': 26,
  '7.1': 27,
  'hexadecagonal': 28
}))

/*----------------------------------------------------------------------------*/

const cloneDeep = (object) => (
  JSON.parse(JSON.stringify(object))
)

const execQuiet = (file, args) => (
  execa(file, args, { 'stdio': 'ignore' })
)

const extractSubs = (filepath, sublang) =>
  ffprobe(filepath).then(streams => {
    const dirname = path.dirname(filepath)
    const basename = path.basename(filepath, path.extname(filepath))
    const subs = streams.filter(({ codec_type }) => codec_type === 'subtitle')
    const seen = new Map

    const maps = []
    subs.forEach((sub, index) => {
      const { disposition } = sub
      const lang = getLang(sub)
      const labels = Object.keys(disposition).filter((key) => key !== 'default' && disposition[key])

      sub.index = index
      if (disposition.default || sublang == null || lang === sublang) {
        const subname = [basename, lang, ...labels, 'srt'].join('.')
        if (!seen.has(subname)) {
          const subpath = path.join(dirname, subname)
          seen.set(subname, subpath)
          maps.push('-map', `0:s:${ sub.index }`, subpath)
        }
      }
    })
    if (!maps.length) {
      return
    }
    return ffmpeg(filepath, ['-vn', '-an', '-scodec', 'srt', ...maps])
      .catch((e) => (
        Promise.all([...seen.values()].map(remove))
          .then(() => { throw e })
      ))
  })

const ffmpeg = (filepath, args) => (
  execQuiet(FFMPEG_PATH, ['-loglevel', 'quiet', '-y', '-i', filepath, ...args])
)

const ffprobe = (filepath) => (
  execa(FFPROBE_PATH, ['-loglevel', 'quiet', '-print_format', 'json', '-show_streams', filepath])
    .then(({ stdout }) => JSON.parse(stdout).streams)
    .catch(() => [])
)

const getChannelLayout = ({ channel_layout }) => (
  channel_layout.split('(')[0]
)

const getLang = (sub) => {
  let language = (sub.tags && sub.tags.language) || 'und'
  language = language.split('-')[0]
  return langMap.get(language) || language
}

const getRank = ({ channel_layout }) => (
  rankMap.get(channel_layout) || 0
)

const series = (tasks) => (
  tasks.reduce((promise, task) => promise.then(task), Promise.resolve())
)

/*----------------------------------------------------------------------------*/

console.log(`Processing video folder ${ foldername }.`)

globby('**/*.{avi,mkv,mov,mp4,mpg,mts,ts,vob}', { 'cwd': inpath, 'realpath': true })
  .then((filepaths) => {
    console.log('Extracting subtitles.')
    return series(filepaths.map((filepath) =>
      () => extractSubs(filepath, 'en')
        .catch(() => {
          const basename = path.basename(filepath)
          console.log(`Failed to extract subtitles for ${ basename }.`)
        })
    ))
  })
  .then(() => {
    // The first pass converts from avi/mkv/other to mp4.
    console.log('Running first pass of mp4_automator/manual.py.')
    return exists(confpath1)
      .then((found) => found || config2.then((object2) => {
        // Create autoProcess.1.ini if it doesn't exist.
        const object1 = cloneDeep(object2)
        Object.assign(object1.MP4, { 'ios-audio': 'True', 'relocate_moov': 'False' })
        return write(confpath1, ini.stringify(object1))
      }))
      .then(() => {
        const spawned = execa(MANUAL_SCRIPT_PATH, ['--auto', '--convertmp4', '--notag', '--config', confpath1, '--input', inpath])
        // Show progress indicator when manually invoked.
        if (group === 'Manual Run') {
          spawned.stdout.pipe(process.stdout)
          return spawned
        }
        // Otherwise, remove progress indicator updates from stdout
        // to avoid cluttering SABnzbd's logs.
        return getStream(spawned.stdout).then((stdout) => {
          stdout = stdout.replace(/\r\[#* *\] \d+%/g, '')
          console.log(stdout)
        })
      })
  })
  .then(() => globby('**/*.mp4', { 'cwd': inpath, 'realpath': true }))
  .then((filepaths) => series(filepaths.map((filepath) =>
    () => ffprobe(filepath).then((streams) => {
      const dirname = path.dirname(filepath)
      const basename = path.basename(filepath)
      const bakpath = path.join(dirname, `${ basename }.original`)

      console.log(`Post processing ${ basename }.`)

      const auds = streams
        .filter((aud) => aud.codec_type === 'audio')
        .map((aud, index) => {
          aud.index = index
          aud.rank = getRank(aud)
          return aud
         })
        .sort((a, b) => {
          // Stable sort audio streams from highest to lowest ranked.
          return a.rank > b.rank ? -1 : (a.rank < b.rank ? 1 : (a.index - b.index))
        })

      const primary = auds.find(({ disposition }) => disposition.default)
      const stereos = auds.filter(({ channel_layout }) => channel_layout === 'stereo')
      const aac = stereos.find(({ codec_name }) => codec_name === 'aac')

      const first = aac || primary || stereos[0]
      const second = auds.find((aud) => getChannelLayout(aud) !== getChannelLayout(first))

      console.log('Restricting to no more than 2 audio streams.')
      const maps = []
      if (first) {
        maps.push('-map', `0:a:${ first.index }`)
      }
      if (second) {
        maps.push('-map', `0:a:${ second.index }`, `-acodec:${ second.index }`, 'ac3')
      }
      if (!maps.length) {
        maps.push('-map', '0:a')
      }
      console.log('Removing embedded subtitles.')
      return move(filepath, bakpath)
        .then(() => ffmpeg(bakpath, ['-codec', 'copy', '-sn', '-map_chapters', '-1', '-map', '0:v', ...maps, filepath]))
        .then(() => remove(bakpath))
    })
  )))
  .then(() => {
    // The second pass adds metadata and relocates the moov atom.
    console.log('Running second pass of mp4_automator/manual.py.')
    const args = ['--auto', '--convertmp4', '--config', confpath2, '--input', inpath]
    if (imdbid) {
      args.push('--imdbid', imdbid)
    }
    // Pipe stdout when manually invoked.
    if (group === 'Manual Run') {
      const spawned = execa(MANUAL_SCRIPT_PATH, args)
      spawned.stdout.pipe(process.stdout)
      return spawned
    }
    return execQuiet(MANUAL_SCRIPT_PATH, args)
  })
  .then(() => {
    console.log('Moving video folder to the watched location.')
    return move(inpath, outpath)
  })
  .then(() => {
    // Since the `SABNZBD` section is configured with `convert = False`
    // invoking SABPostProcess.py will simply notify CouchPotato and Sonarr.
    const manager = category === 'movies' ? 'CouchPotato' : 'Sonarr'
    console.log(`Notifying ${ manager } renamer.`)
    const spawned = execa(SAB_SCRIPT_PATH, argv)
    spawned.stdout.pipe(process.stdout)
    return spawned
  })
  .then(() => {
    console.log('Finding renamed video folder.')
    const cwd = category === 'movies' ? COUCH_COMPLETE_PATH : SONARR_COMPLETE_PATH
    return globby('**/', { 'cwd': cwd, 'realpath': true })
      .then((dirpaths) => dirpaths
        .map((dirpath) => ({
          'value': dirpath,
          'time': fs.statSync(dirpath).mtime.getTime()
        }))
        .sort((a, b) => {
          // Sort folders from newest to oldest.
          return b.time - a.time
        })
        .map(({ value }) => value)[0] || cwd)
  })
  .then((dirpath) =>
    globby('*.srt', { 'cwd': dirpath, 'realpath': true })
      .then((subpaths) => {
        console.log('Scanning for misnamed subtitles.')
        return series(subpaths.map((subpath) =>
          () => {
            const dirname = path.dirname(subpath)
            const basename = path.basename(subpath)
            const parts = basename.split('.')
            if (parts[1] !== 'en') {
              // Add "en" language code to subtitle name.
              parts.splice(1, 0, 'en')
              const rename = parts.join('.')
              return Promise.resolve()
                .then(() => console.log(`Renaming ${ basename } to ${ rename }.`))
                .then(() => move(subpath, path.join(dirname, rename)))
            }
          }
        ))
      })
      .then(() => globby(['*', '!*.{avi,mkv,mov,mp4,mpg,mts,srt,ts,vob}'], { 'cwd': dirpath, 'realpath': true }))
      .then((filepaths) => {
        console.log('Scanning for leftover files.')
        return series(filepaths.map((filepath) => {
          const basename = path.basename(filepath)
          return Promise.resolve()
            .then(() => console.log(`Removing ${ basename }.`))
            .then(() => remove(filepath))
        }))
      })
  )
  .then(() => console.log('Completed.'))
  .catch((e) => console.error(e))
<commit_msg>Ensure `pify` works with `fs.exists`.<commit_after>#!/usr/bin/env node
'use strict'

const execa = require('execa')
const fs = require('fs-extra')
const getStream = require('get-stream')
const globby = require('globby')
const ini = require('ini')
const path = require('path')
const pify = require('pify')

const unary = (func) => (a) => func(a)
const binary = (func) => (a, b) => func(a, b)

const exists = unary(pify((a, b) => fs.exists(a, (c) => b(null, c))))
const move = binary(pify(fs.move))
const read = binary(pify(fs.readFile))
const remove = unary(pify(fs.remove))
const write = binary(pify(fs.writeFile))

const FFMPEG_PATH = '/opt/bin/ffmpeg'
const FFPROBE_PATH = '/opt/bin/ffprobe'

const COUCH_COMPLETE_PATH = '/share/CACHEDEV1_DATA/Multimedia/Movies'
const SONARR_COMPLETE_PATH = '/share/CACHEDEV1_DATA/Multimedia/TV'
const WATCH_PATH = '/share/CACHEDEV1_DATA/Download/complete'

const MANUAL_SCRIPT_PATH = '/share/CACHEDEV1_DATA/scripts/manual.py'
const SAB_SCRIPT_PATH = '/share/CACHEDEV1_DATA/scripts/SABPostProcess.py'

const confpath1 = path.join(__dirname, 'autoProcess.1.ini')
const confpath2 = path.join(__dirname, 'autoProcess.ini')
const config2 = read(confpath2, 'utf8').then(ini.parse)

const argv = process.argv.slice(2)
const inpath = path.resolve(argv[0])
const foldername = path.basename(inpath)
const imdbid = (/\btt\d{7,}/.exec(foldername) || [''])[0]

if (argv.length < 3) {
  argv.length = 7
  // The name of the job name without path or file extension.
  argv[2] = foldername
  // The indexer's report number. It's not used by SABPostProcess.py.
  argv[3] = 0
  // The user-defined category used to signal which manager is notified.
  argv[4] = argv[1] || inpath.toLowerCase().split(path.sep).includes('tv') ? 'tv' : 'movies'
  // The group the NZB was posted in, e.g. alt.binaries.x. We use it to detect a "Manual Run".
  argv[5] = 'Manual Run'
  // The status of post processing (0 = OK, 1=failed verification, 2=failed unpack, 3=1+2).
  argv[6] = 0
  // The name of the NZB file used by SABPostProcess.py to detect a "Manual Run".
  argv[1] = `${ foldername }.nzb`
}
const category = argv[4]
const group = argv[5]
const outpath = path.join(WATCH_PATH, category, foldername)

const langMap = new Map(Object.entries({
  'eng': 'en',
  'und': 'en'
}))

const rankMap = new Map(Object.entries({
  'mono': 1,
  'stereo': 2,
  'downmix': 3,
  '2.1': 4,
  '3.0(back)': 5,
  '3.0': 6,
  '3.1': 7,
  'quad(side)': 8,
  'quad': 9,
  '4.0': 10,
  '4.1': 11,
  '5.0(side)': 12,
  '5.0': 13,
  '5.1(side)': 14,
  '5.1': 15,
  'hexagonal': 16,
  '6.0(front)':  17,
  '6.0': 18,
  '6.1(back)': 19,
  '6.1(front)': 20,
  '6.1': 21,
  '7.0(front)': 22,
  '7.0': 23,
  'octagonal': 24,
  '7.1(wide-side)': 25,
  '7.1(wide)': 26,
  '7.1': 27,
  'hexadecagonal': 28
}))

/*----------------------------------------------------------------------------*/

const cloneDeep = (object) => (
  JSON.parse(JSON.stringify(object))
)

const execQuiet = (file, args) => (
  execa(file, args, { 'stdio': 'ignore' })
)

const extractSubs = (filepath, sublang) =>
  ffprobe(filepath).then(streams => {
    const dirname = path.dirname(filepath)
    const basename = path.basename(filepath, path.extname(filepath))
    const subs = streams.filter(({ codec_type }) => codec_type === 'subtitle')
    const seen = new Map

    const maps = []
    subs.forEach((sub, index) => {
      const { disposition } = sub
      const lang = getLang(sub)
      const labels = Object.keys(disposition).filter((key) => key !== 'default' && disposition[key])

      sub.index = index
      if (disposition.default || sublang == null || lang === sublang) {
        const subname = [basename, lang, ...labels, 'srt'].join('.')
        if (!seen.has(subname)) {
          const subpath = path.join(dirname, subname)
          seen.set(subname, subpath)
          maps.push('-map', `0:s:${ sub.index }`, subpath)
        }
      }
    })
    if (!maps.length) {
      return
    }
    return ffmpeg(filepath, ['-vn', '-an', '-scodec', 'srt', ...maps])
      .catch((e) => (
        Promise.all([...seen.values()].map(remove))
          .then(() => { throw e })
      ))
  })

const ffmpeg = (filepath, args) => (
  execQuiet(FFMPEG_PATH, ['-loglevel', 'quiet', '-y', '-i', filepath, ...args])
)

const ffprobe = (filepath) => (
  execa(FFPROBE_PATH, ['-loglevel', 'quiet', '-print_format', 'json', '-show_streams', filepath])
    .then(({ stdout }) => JSON.parse(stdout).streams)
    .catch(() => [])
)

const getChannelLayout = ({ channel_layout }) => (
  channel_layout.split('(')[0]
)

const getLang = (sub) => {
  let language = (sub.tags && sub.tags.language) || 'und'
  language = language.split('-')[0]
  return langMap.get(language) || language
}

const getRank = ({ channel_layout }) => (
  rankMap.get(channel_layout) || 0
)

const series = (tasks) => (
  tasks.reduce((promise, task) => promise.then(task), Promise.resolve())
)

/*----------------------------------------------------------------------------*/

console.log(`Processing video folder ${ foldername }.`)

globby('**/*.{avi,mkv,mov,mp4,mpg,mts,ts,vob}', { 'cwd': inpath, 'realpath': true })
  .then((filepaths) => {
    console.log('Extracting subtitles.')
    return series(filepaths.map((filepath) =>
      () => extractSubs(filepath, 'en')
        .catch(() => {
          const basename = path.basename(filepath)
          console.log(`Failed to extract subtitles for ${ basename }.`)
        })
    ))
  })
  .then(() => {
    // The first pass converts from avi/mkv/other to mp4.
    console.log('Running first pass of mp4_automator/manual.py.')
    return exists(confpath1)
      .then((found) => found || config2.then((object2) => {
        // Create autoProcess.1.ini if it doesn't exist.
        const object1 = cloneDeep(object2)
        Object.assign(object1.MP4, { 'ios-audio': 'True', 'relocate_moov': 'False' })
        return write(confpath1, ini.stringify(object1))
      }))
      .then(() => {
        const spawned = execa(MANUAL_SCRIPT_PATH, ['--auto', '--convertmp4', '--notag', '--config', confpath1, '--input', inpath])
        // Show progress indicator when manually invoked.
        if (group === 'Manual Run') {
          spawned.stdout.pipe(process.stdout)
          return spawned
        }
        // Otherwise, remove progress indicator updates from stdout
        // to avoid cluttering SABnzbd's logs.
        return getStream(spawned.stdout).then((stdout) => {
          stdout = stdout.replace(/\r\[#* *\] \d+%/g, '')
          console.log(stdout)
        })
      })
  })
  .then(() => globby('**/*.mp4', { 'cwd': inpath, 'realpath': true }))
  .then((filepaths) => series(filepaths.map((filepath) =>
    () => ffprobe(filepath).then((streams) => {
      const dirname = path.dirname(filepath)
      const basename = path.basename(filepath)
      const bakpath = path.join(dirname, `${ basename }.original`)

      console.log(`Post processing ${ basename }.`)

      const auds = streams
        .filter((aud) => aud.codec_type === 'audio')
        .map((aud, index) => {
          aud.index = index
          aud.rank = getRank(aud)
          return aud
         })
        .sort((a, b) => {
          // Stable sort audio streams from highest to lowest ranked.
          return a.rank > b.rank ? -1 : (a.rank < b.rank ? 1 : (a.index - b.index))
        })

      const primary = auds.find(({ disposition }) => disposition.default)
      const stereos = auds.filter(({ channel_layout }) => channel_layout === 'stereo')
      const aac = stereos.find(({ codec_name }) => codec_name === 'aac')

      const first = aac || primary || stereos[0]
      const second = auds.find((aud) => getChannelLayout(aud) !== getChannelLayout(first))

      console.log('Restricting to no more than 2 audio streams.')
      const maps = []
      if (first) {
        maps.push('-map', `0:a:${ first.index }`)
      }
      if (second) {
        maps.push('-map', `0:a:${ second.index }`, `-acodec:${ second.index }`, 'ac3')
      }
      if (!maps.length) {
        maps.push('-map', '0:a')
      }
      console.log('Removing embedded subtitles.')
      return move(filepath, bakpath)
        .then(() => ffmpeg(bakpath, ['-codec', 'copy', '-sn', '-map_chapters', '-1', '-map', '0:v', ...maps, filepath]))
        .then(() => remove(bakpath))
    })
  )))
  .then(() => {
    // The second pass adds metadata and relocates the moov atom.
    console.log('Running second pass of mp4_automator/manual.py.')
    const args = ['--auto', '--convertmp4', '--config', confpath2, '--input', inpath]
    if (imdbid) {
      args.push('--imdbid', imdbid)
    }
    // Pipe stdout when manually invoked.
    if (group === 'Manual Run') {
      const spawned = execa(MANUAL_SCRIPT_PATH, args)
      spawned.stdout.pipe(process.stdout)
      return spawned
    }
    return execQuiet(MANUAL_SCRIPT_PATH, args)
  })
  .then(() => {
    console.log('Moving video folder to the watched location.')
    return move(inpath, outpath)
  })
  .then(() => {
    // Since the `SABNZBD` section is configured with `convert = False`
    // invoking SABPostProcess.py will simply notify CouchPotato and Sonarr.
    const manager = category === 'movies' ? 'CouchPotato' : 'Sonarr'
    console.log(`Notifying ${ manager } renamer.`)
    const spawned = execa(SAB_SCRIPT_PATH, argv)
    spawned.stdout.pipe(process.stdout)
    return spawned
  })
  .then(() => {
    console.log('Finding renamed video folder.')
    const cwd = category === 'movies' ? COUCH_COMPLETE_PATH : SONARR_COMPLETE_PATH
    return globby('**/', { 'cwd': cwd, 'realpath': true })
      .then((dirpaths) => dirpaths
        .map((dirpath) => ({
          'value': dirpath,
          'time': fs.statSync(dirpath).mtime.getTime()
        }))
        .sort((a, b) => {
          // Sort folders from newest to oldest.
          return b.time - a.time
        })
        .map(({ value }) => value)[0] || cwd)
  })
  .then((dirpath) =>
    globby('*.srt', { 'cwd': dirpath, 'realpath': true })
      .then((subpaths) => {
        console.log('Scanning for misnamed subtitles.')
        return series(subpaths.map((subpath) =>
          () => {
            const dirname = path.dirname(subpath)
            const basename = path.basename(subpath)
            const parts = basename.split('.')
            if (parts[1] !== 'en') {
              // Add "en" language code to subtitle name.
              parts.splice(1, 0, 'en')
              const rename = parts.join('.')
              return Promise.resolve()
                .then(() => console.log(`Renaming ${ basename } to ${ rename }.`))
                .then(() => move(subpath, path.join(dirname, rename)))
            }
          }
        ))
      })
      .then(() => globby(['*', '!*.{avi,mkv,mov,mp4,mpg,mts,srt,ts,vob}'], { 'cwd': dirpath, 'realpath': true }))
      .then((filepaths) => {
        console.log('Scanning for leftover files.')
        return series(filepaths.map((filepath) => {
          const basename = path.basename(filepath)
          return Promise.resolve()
            .then(() => console.log(`Removing ${ basename }.`))
            .then(() => remove(filepath))
        }))
      })
  )
  .then(() => console.log('Completed.'))
  .catch((e) => console.error(e))
<|endoftext|>


<commit_before>/*

	tdapp_controller_main.js

*/
var tdapp = require('./tdapp');

tdapp.controller("MainCtrl",function($scope,$timeout,$interval,$http,$cookies,$window,appdata,TDMgr,Backend,Autologin){

	// Injections

	Autologin.setScope($scope);
	Backend.setScope($scope);

	// General Done Filter

	$scope.showdone = false;
	$scope.showdonetext = "Show Done";

	// Go to settings

	$scope.gosettings = function(){
		$window.location = "/#/settings";
	}

	// Show and hide custommenu (animated via jquery)

	$scope.tmpcustommenu = 0;
	$scope.showcustommenu = function(){
		if($scope.tmpcustommenu==0){
			$scope.tmpcustommenu=1;
			$("#customnavbaricon").attr("src","images/arrow-left-3x.png");
			if(navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/)) {
				$('body').scrollTop(0);
				$(".custommenu").animate({height:"140px"},500);
			} else {
				if($('html').scrollTop()>64){
					$('html').animate({scrollTop:0},500,function(){
						$(".custommenu").animate({height:"140px"},500);
					});
				} else {
					$('html').scrollTop(0);
					$(".custommenu").animate({height:"140px"},500);
				}
			}
		} else {
			$scope.tmpcustommenu=0;
			$("#customnavbaricon").attr("src","images/menu-3x.png");
			$(".custommenu").animate({height:"0px"},500);
		}
	}

	// Logout

	$scope.dologout = function(){
		$(".todota").css("visibility","hidden");
		$(".todotab").css("visibility","hidden");
		$cookies.remove(appdata.cookiename);
		TDMgr.clearTodos();
		$window.location = "/#/working";
		$timeout(function(){
			$window.location = "/#/login";
		},1000);
		$timeout(function(){
			$("#liusername").focus();
		},1128);
	}

	// Keyboard

	$scope.newtodoKeydown = function(e){
		var k = e.keyCode;
		if(k==13){//ret
			e.preventDefault();
			if($scope.newtodotxt!=""){
				var newtodo = {};
				newtodo.topic = $scope.newtodotxt;
				newtodo.done = false;
				$scope.newtodotxt = "";
				Backend.postTodo(newtodo);
				TDMgr.addTodoObj(newtodo);
				if($scope.showdone){
					$scope.showdone = false;
					$scope.showdonetext = "Show Done";
				}
				if(newtodo.tag!=undefined){
					$scope.filtertag = newtodo.tag;
				} else {
					$scope.filtertag = "All";
				}
				$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
				window.scrollTo(0,0);
				$("#todotxta").focus();
			}
		} else
		if(k==9){//tab
			e.preventDefault();
		}
	}

	$scope.todolineKeydown = function(e,id){
		var k = e.keyCode;
		if(k==13){//ret
			e.preventDefault();
			var currentTodo = $('#todoid'+id);
			currentTodo.blur();
			var obj = TDMgr.getTodoById(id);
			if(obj!=undefined){
				obj.topic = currentTodo.html();
				Backend.putTodo(obj);
				var oldtag = obj.tag;
				TDMgr.checkForHashtag(obj);
				if(oldtag!=undefined){
					var ttd = TDMgr.getTodosByTag(oldtag);
					if(ttd.length<=0){
						TDMgr.tags.splice(TDMgr.tags.indexOf(oldtag),1);
					}
				}
				$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
			}
		}
	}

	// Todo functions

	$scope.seltodoline = function(id){
		$("#todoid"+id).focus();
	}

	$scope.deltodo = function(obj){ // No animation
		obj.deleted = true;
		Backend.delTodo(obj);
		TDMgr.delTodo(obj);
		$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
	}

	$scope.togDone = function(obj){
		TDMgr.togPreDone(obj);
		$timeout(function(){
			TDMgr.togDone(obj); // Toggle todo local (within TDMgr)
			if(obj.done){ // Toggle Todo on the server
				Backend.doneTodo(obj);
			} else {
				Backend.undoneTodo(obj);
			}
			$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
		},1000);
	}

	// Filter function

	$scope.togShowdone = function(){
		$scope.showdone = !$scope.showdone;
		if($scope.showdone){
			$scope.showdonetext = "Show Open";
		} else {
			$scope.showdonetext = "Show Done";
		}
		$scope.todos=TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
	}

	// Tags

	$scope.getTodosByTag = TDMgr.getTodosByTag;

	// Finish

	Autologin.check(); // do automatic login if cookie/token is available
});
<commit_msg>Correct contenteditable behaviour<commit_after>/*

	tdapp_controller_main.js

*/
var tdapp = require('./tdapp');

tdapp.controller("MainCtrl",function($scope,$timeout,$interval,$http,$cookies,$window,appdata,TDMgr,Backend,Autologin){

	// Injections

	Autologin.setScope($scope);
	Backend.setScope($scope);

	// General Done Filter

	$scope.showdone = false;
	$scope.showdonetext = "Show Done";

	// Go to settings

	$scope.gosettings = function(){
		$window.location = "/#/settings";
	}

	// Show and hide custommenu (animated via jquery)

	$scope.tmpcustommenu = 0;
	$scope.showcustommenu = function(){
		if($scope.tmpcustommenu==0){
			$scope.tmpcustommenu=1;
			$("#customnavbaricon").attr("src","images/arrow-left-3x.png");
			if(navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/)) {
				$('body').scrollTop(0);
				$(".custommenu").animate({height:"140px"},500);
			} else {
				if($('html').scrollTop()>64){
					$('html').animate({scrollTop:0},500,function(){
						$(".custommenu").animate({height:"140px"},500);
					});
				} else {
					$('html').scrollTop(0);
					$(".custommenu").animate({height:"140px"},500);
				}
			}
		} else {
			$scope.tmpcustommenu=0;
			$("#customnavbaricon").attr("src","images/menu-3x.png");
			$(".custommenu").animate({height:"0px"},500);
		}
	}

	// Logout

	$scope.dologout = function(){
		$(".todota").css("visibility","hidden");
		$(".todotab").css("visibility","hidden");
		$cookies.remove(appdata.cookiename);
		TDMgr.clearTodos();
		$window.location = "/#/working";
		$timeout(function(){
			$window.location = "/#/login";
		},1000);
		$timeout(function(){
			$("#liusername").focus();
		},1128);
	}

	// Keyboard

	$scope.newtodoKeydown = function(e){
		var k = e.keyCode;
		if(k==13){//ret
			e.preventDefault();
			if($scope.newtodotxt!=""){
				var newtodo = {};
				newtodo.topic = $scope.newtodotxt;
				newtodo.done = false;
				$scope.newtodotxt = "";
				Backend.postTodo(newtodo);
				TDMgr.addTodoObj(newtodo);
				if($scope.showdone){
					$scope.showdone = false;
					$scope.showdonetext = "Show Done";
				}
				if(newtodo.tag!=undefined){
					$scope.filtertag = newtodo.tag;
				} else {
					$scope.filtertag = "All";
				}
				$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
				window.scrollTo(0,0);
				$("#todotxta").focus();
			}
		} else
		if(k==9){//tab
			e.preventDefault();
		}
	}

	$scope.todolineKeydown = function(e,id){
		var k = e.keyCode;
		if(k==13){//ret
			e.preventDefault();
			var currentTodo = $('#todoid'+id);
			// Correct contenteditable behaviour
			currentTodo.html(currentTodo.html().replace('<br>',''));
			currentTodo.blur();
			var obj = TDMgr.getTodoById(id);
			if(obj!=undefined){
				obj.topic = currentTodo.html();
				Backend.putTodo(obj);
				var oldtag = obj.tag;
				TDMgr.checkForHashtag(obj);
				if(oldtag!=undefined){
					var ttd = TDMgr.getTodosByTag(oldtag);
					if(ttd.length<=0){
						TDMgr.tags.splice(TDMgr.tags.indexOf(oldtag),1);
					}
				}
				$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
			}
		}
	}

	// Todo functions

	$scope.seltodoline = function(id){
		$("#todoid"+id).focus();
	}

	$scope.deltodo = function(obj){ // No animation
		obj.deleted = true;
		Backend.delTodo(obj);
		TDMgr.delTodo(obj);
		$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
	}

	$scope.togDone = function(obj){
		TDMgr.togPreDone(obj);
		$timeout(function(){
			TDMgr.togDone(obj); // Toggle todo local (within TDMgr)
			if(obj.done){ // Toggle Todo on the server
				Backend.doneTodo(obj);
			} else {
				Backend.undoneTodo(obj);
			}
			$scope.todos = TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
		},1000);
	}

	// Filter function

	$scope.togShowdone = function(){
		$scope.showdone = !$scope.showdone;
		if($scope.showdone){
			$scope.showdonetext = "Show Open";
		} else {
			$scope.showdonetext = "Show Done";
		}
		$scope.todos=TDMgr.getTodosByTag($scope.filtertag,$scope.showdone);
	}

	// Tags

	$scope.getTodosByTag = TDMgr.getTodosByTag;

	// Finish

	Autologin.check(); // do automatic login if cookie/token is available
});
<|endoftext|>


<commit_before>/**
 * Analytics Settings Edit component tests.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import { render, waitFor, createTestRegistry } from '../../../../../../tests/js/test-utils';
import { STORE_NAME } from '../../datastore/constants';
import { CORE_SITE } from '../../../../googlesitekit/datastore/site/constants';
import { CORE_MODULES } from '../../../../googlesitekit/modules/datastore/constants';
import SettingsEdit from './SettingsEdit';
import * as fixtures from '../../datastore/__fixtures__';

describe( 'SettingsEdit', () => {
	it( 'sets the account ID and property ID of an existing tag when present', async () => {
		fetchMock.get(
			/tagmanager\/data\/settings/,
			{ body: {}, status: 200 },
		);
		fetchMock.getOnce(
			/^\/google-site-kit\/v1\/modules\/analytics-4\/data\/properties/,
			{ body: [], status: 200 },
		);

		const registry = createTestRegistry();
		const existingTag = {};
		const { accounts, properties, profiles } = fixtures.accountsPropertiesProfiles;
		existingTag.accountID = profiles[ 0 ].accountId; // eslint-disable-line sitekit/acronym-case
		existingTag.propertyID = profiles[ 0 ].webPropertyId; // eslint-disable-line sitekit/acronym-case
		const { accountID, propertyID } = existingTag;
		registry.dispatch( STORE_NAME ).setSettings( {} );
		registry.dispatch( CORE_MODULES ).receiveGetModules( [] );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( accounts );
		registry.dispatch( STORE_NAME ).receiveGetProperties( properties, { accountID } );
		registry.dispatch( STORE_NAME ).receiveGetProfiles( profiles, { accountID, propertyID } );
		registry.dispatch( STORE_NAME ).receiveGetExistingTag( existingTag.propertyID );
		registry.dispatch( STORE_NAME ).receiveGetTagPermission( {
			accountID: existingTag.accountID,
			permission: true,
		}, { propertyID: existingTag.propertyID } );
		registry.dispatch( CORE_SITE ).receiveSiteInfo( {} );

		await waitFor( () => {
			render( <SettingsEdit />, { registry } );
		} );

		expect( registry.select( STORE_NAME ).getAccountID() ).toBe( existingTag.accountID );
		expect( registry.select( STORE_NAME ).getPropertyID() ).toBe( existingTag.propertyID );
		expect( registry.select( STORE_NAME ).hasErrors() ).toBeFalsy();
	} );
} );
<commit_msg>Refactor tests for the SettingsEdit component.<commit_after>/**
 * Analytics Settings Edit component tests.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import { render, waitFor, createTestRegistry } from '../../../../../../tests/js/test-utils';
import { STORE_NAME } from '../../datastore/constants';
import { CORE_MODULES } from '../../../../googlesitekit/modules/datastore/constants';
import SettingsEdit from './SettingsEdit';
import * as fixtures from '../../datastore/__fixtures__';
import { provideSiteInfo } from '../../../../../../tests/js/utils';

describe( 'SettingsEdit', () => {
	let registry;

	beforeEach( () => {
		registry = createTestRegistry();
		provideSiteInfo( registry );
	} );

	it( 'sets the account ID and property ID of an existing tag when present', async () => {
		fetchMock.get( /tagmanager\/data\/settings/, { body: {} } );
		fetchMock.getOnce( /^\/google-site-kit\/v1\/modules\/analytics-4\/data\/properties/, { body: [] } );

		const { accounts, properties, profiles } = fixtures.accountsPropertiesProfiles;
		const existingTag = {
			/* eslint-disable sitekit/acronym-case */
			accountID: profiles[ 0 ].accountId,
			propertyID: profiles[ 0 ].webPropertyId,
			/* eslint-enable */
		};

		const { accountID, propertyID } = existingTag;

		registry.dispatch( CORE_MODULES ).receiveGetModules( [] );

		registry.dispatch( STORE_NAME ).setSettings( {} );
		registry.dispatch( STORE_NAME ).receiveGetAccounts( accounts );
		registry.dispatch( STORE_NAME ).receiveGetProperties( properties, { accountID } );
		registry.dispatch( STORE_NAME ).receiveGetProfiles( profiles, { accountID, propertyID } );

		registry.dispatch( STORE_NAME ).receiveGetExistingTag( existingTag.propertyID );
		registry.dispatch( STORE_NAME ).receiveGetTagPermission( {
			accountID: existingTag.accountID,
			permission: true,
		}, { propertyID: existingTag.propertyID } );

		await waitFor( () => {
			render( <SettingsEdit />, { registry } );
		} );

		expect( registry.select( STORE_NAME ).getAccountID() ).toBe( existingTag.accountID );
		expect( registry.select( STORE_NAME ).getPropertyID() ).toBe( existingTag.propertyID );
		expect( registry.select( STORE_NAME ).hasErrors() ).toBeFalsy();
	} );
} );
<|endoftext|>


<commit_before>import React from 'react'
import {Link} from 'react-router'

const productData = [{
    id: 1,
    name: 'Thunderbird',
    description: 'Flying beast that can sense danger, and create storms as it flies. Its tail feathers were used by Shikoba Wolfe to create powerful wands, particularly good for Transfiguration.',
    colors: ['gray', 'white', 'black', 'red'],
    size: 'L',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/6F2Hrc4vgASui8mcQMYKC2/88edf4f0d933a7cea6e36b9b0b66613d/Thunderbird_Fantastic_Beasts_CC_Trailer_WM.JPG?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 20,
    magicalAbilities: ['weather manipulation', 'fear sensing', 'flying'],
    lifespan: 60,
    price: 2000,
    breed_id:1
  },
  { id: 2,
    name: 'Bowtruckles',
    description: 'A small twig-like creature that guards wand-wood trees.',
    colors: ['green', 'brown'],
    size: 'XS',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/CHqGFAIkwK2y2meEMgQAY/9562ab7cb3f75af827bd4c3ffa1c2eea/FTB203_FANTASTIC_BEASTS_AND_WHERE_TO_FIND_THEM_A_NEW_HERO_FEATURETTE_2255.jpg?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 100,
    magicalAbilities: ['natural camouflage'],
    lifespan: 5,
    price: 100,
    breed_id: 2
  },
  { id: 3,
    name: 'Hippogriffs',
    description: 'Half horse, half eagle creatures, immensely proud and extremely dangerous.',
    colors: ['brown', 'white', 'black', 'silver'],
    size: 'L',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/4QbMOZOaww8YAMWaoOuAys/d3368834aa24b460d375f10774c55fea/Buckbeak_WB_F3_ConceptOfHarryRidingFlyingBuckbeak_Illust_100615_Land.jpg?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 50,
    magicalAbilities: ['flying'],
    lifespan: 70,
    price: 100,
    breed_id: 3
  },
  { id: 4,
    name: 'Niffler',
    description: 'Long-snouted, burrowing creatures native to Britain with a penchant for anything shiny.',
    colors: ['brown', 'white', 'black'],
    size: 'S',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/3x8xkyxFqU0w6WaMAuUmsK/69b6776507fba83b3f90a4c59475440c/FB-TRL2-niffler_alt.jpg?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 100,
    magicalAbilities: ['flying'],
    lifespan: 70,
    price: 100,
    breed_id: 4
  }
]

export default (props) => {
  return (

Creatures

        {productData.map(product => {
        return (

          {/* add onclick handler to single product view */}
           clickedAlbum(album.id)}>

                {product.price} Galleons

              INSERT REVIEW

)
        })}

  )
}
<commit_msg>finish working products component<commit_after>import React from 'react'
import {Link} from 'react-router'

const productData = [{
    id: 1,
    name: 'Thunderbird',
    description: 'Flying beast that can sense danger, and create storms as it flies. Its tail feathers were used by Shikoba Wolfe to create powerful wands, particularly good for Transfiguration.',
    colors: ['gray', 'white', 'black', 'red'],
    size: 'L',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/6F2Hrc4vgASui8mcQMYKC2/88edf4f0d933a7cea6e36b9b0b66613d/Thunderbird_Fantastic_Beasts_CC_Trailer_WM.JPG?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 20,
    magicalAbilities: ['weather manipulation', 'fear sensing', 'flying'],
    lifespan: 60,
    price: 2000,
    breed_id:1
  },
  { id: 2,
    name: 'Bowtruckles',
    description: 'A small twig-like creature that guards wand-wood trees.',
    colors: ['green', 'brown'],
    size: 'XS',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/CHqGFAIkwK2y2meEMgQAY/9562ab7cb3f75af827bd4c3ffa1c2eea/FTB203_FANTASTIC_BEASTS_AND_WHERE_TO_FIND_THEM_A_NEW_HERO_FEATURETTE_2255.jpg?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 100,
    magicalAbilities: ['natural camouflage'],
    lifespan: 5,
    price: 100,
    breed_id: 2
  },
  { id: 3,
    name: 'Hippogriffs',
    description: 'Half horse, half eagle creatures, immensely proud and extremely dangerous.',
    colors: ['brown', 'white', 'black', 'silver'],
    size: 'L',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/4QbMOZOaww8YAMWaoOuAys/d3368834aa24b460d375f10774c55fea/Buckbeak_WB_F3_ConceptOfHarryRidingFlyingBuckbeak_Illust_100615_Land.jpg?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 50,
    magicalAbilities: ['flying'],
    lifespan: 70,
    price: 100,
    breed_id: 3
  },
  { id: 4,
    name: 'Niffler',
    description: 'Long-snouted, burrowing creatures native to Britain with a penchant for anything shiny.',
    colors: ['brown', 'white', 'black'],
    size: 'S',
    pictureURL: 'https://images.pottermore.com/bxd3o8b291gf/3x8xkyxFqU0w6WaMAuUmsK/69b6776507fba83b3f90a4c59475440c/FB-TRL2-niffler_alt.jpg?w=550&h=550&fit=thumb&f=center&q=85',
    inventory: 100,
    magicalAbilities: ['flying'],
    lifespan: 70,
    price: 100,
    breed_id: 4
  }
]

export default (props) => {
  console.log('got here=======')
  return (

Creatures

        {productData.map(product => {
        return (

          {/* add onclick handler to single product view */}
           clickedAlbum(album.id)}>

                {product.price} Galleons

              INSERT REVIEW

)
        })}

  )
}
<|endoftext|>


<commit_before>(function () {

"use strict";

Dashboard.Client = Marbles.Utils.createClass({
	displayName: "Client",

	mixins: [{
		ctor: {
			middleware: [
				Marbles.HTTP.Middleware.SerializeJSON,
				Marbles.HTTP.Middleware.WithCredentials
			]
		}
	}],

	willInitialize: function (endpoints) {
		this.endpoints = endpoints;
		this.__cachedResponses = {};
	},

	performRequest: function (method, args) {
		if ( !args.url ) {
				var err = new Error(this.constructor.displayName +".prototype.performRequest(): Can't make request without URL");
			setTimeout(function () {
				throw err;
			}.bind(this), 0);
			return Promise.reject(err);
		}

		var cacheResponse = args.cacheResponse;
		var cachedResponseKey = method + args.url;
		var cachedRepsonses = this.__cachedResponses;
		if (cacheResponse && cachedRepsonses[cachedResponseKey]) {
			return Promise.resolve(cachedRepsonses[cachedResponseKey]);
		}

		var middleware = args.middleware || [];
		delete args.middleware;

		return Marbles.HTTP(Marbles.Utils.extend({
			method: method,
			middleware: [].concat(this.constructor.middleware).concat(middleware),
		}, args)).then(function (args) {
			var res = args[0];
			var xhr = args[1];
			return new Promise(function (resolve, reject) {
				if (xhr.status >= 200 && xhr.status < 400) {
					if (cacheResponse) {
						cachedRepsonses[cachedResponseKey] = [res, xhr];
					}
					resolve([res, xhr]);
				} else {
					if (xhr.status === 401) {
						Dashboard.config.fetch();
					}

					reject([res, xhr]);
				}
			});
		});
	},

	performControllerRequest: function (method, args) {
		var controllerKey = (Dashboard.config.user || {}).controller_key;
		return this.performRequest(method, Marbles.Utils.extend({}, args, {
			middleware: [
				Marbles.HTTP.Middleware.BasicAuth("", controllerKey)
			],
			url: this.endpoints.cluster_controller + args.url
		}));
	},

	login: function (token) {
		return this.performRequest('POST', {
			url: this.endpoints.login,
			body: {
				token: token
			},
			headers: {
				'Content-Type': 'application/json'
			}
		}).then(function (args) {
			return Dashboard.config.fetch().then(function () {
				return args;
			});
		});
	},

	logout: function () {
		return this.performRequest('DELETE', {
			url: this.endpoints.logout
		}).then(function (args) {
			return Dashboard.config.fetch().then(function () {
				return args;
			});
		});
	},

	ping: function (endpoint, protocol) {
		endpoint = window.location.host.replace("dashboard", endpoint);
		endpoint = protocol + "://" + endpoint + "/ping";

		return this.performRequest('GET', {
			url: endpoint
		});
	},

	getApps: function () {
		return this.performControllerRequest('GET', {
			url: "/apps"
		});
	},

	getApp: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId)
		});
	},

	getAppRelease: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/release"
		});
	},

	getAppFormation: function (appId, releaseId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/formations/"+ encodeURIComponent(releaseId)
		});
	},

	getAppJobs: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/jobs"
		});
	},

	getAppRoutes: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/routes"
		});
	},

	getAppResources: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/resources"
		});
	},

	createAppRoute: function (appId, data) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ encodeURIComponent(appId) +"/routes",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deleteAppRoute: function (appId, routeType, routeId) {
		return this.performControllerRequest('DELETE', {
			url: "/apps/"+ appId +"/routes/"+ routeType +"/"+ routeId
		});
	},

	createApp: function (data) {
		return this.performControllerRequest('POST', {
			url: "/apps",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	updateApp: function (appId, data) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ encodeURIComponent(appId),
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deleteApp: function (appId) {
		return this.performControllerRequest('DELETE', {
			url: "/apps/"+ encodeURIComponent(appId)
		});
	},

	createAppDatabase: function (data) {
		return this.performControllerRequest('POST', {
			url: "/providers/postgres/resources",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createArtifact: function (data) {
		return this.performControllerRequest('POST', {
			url: "/artifacts",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createRelease: function (data) {
		return this.performControllerRequest('POST', {
			url: "/releases",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deployAppRelease: function (appId, releaseId) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ appId +"/deploy",
			body: {id: releaseId},
			headers: {
				'Content-Type': 'application/json'
			}
		}).then(function (args) {
			var res = args[0];
			if (res.finished_at) {
				return args;
			}
			return this.waitForDeployment(res.id).then(function () {
				return args;
			}, function (err) {
				return Promise.reject([err]);
			});
		}.bind(this));
	},

	waitForDeployment: function (deploymentId) {
		if ( !window.hasOwnProperty('EventSource') ) {
			return Promise.reject('window.EventSource not defined');
		}

		var controllerKey = (Dashboard.config.user || {}).controller_key;
		var url = this.endpoints.cluster_controller + "/deployments/"+ encodeURIComponent(deploymentId);
		url = url + "?key="+ encodeURIComponent(controllerKey);
		return new Promise(function (resolve, reject) {
			var es = new window.EventSource(url, {withCredentials: true});

			setTimeout(function () {
				reject("Timed out waiting for deployment completion");
				es.close();
			}, 10000);

			es.addEventListener("error", function (e) {
				reject(e);
				es.close();
			});
			es.addEventListener("complete", function () {
				resolve();
				es.close();
			});
			es.addEventListener("message", function (e) {
				var data = JSON.parse(e.data);
				if (data.status === "complete") {
					resolve();
					es.close();
				}
			});
		}.bind(this));
	},

	createAppRelease: function (appId, data) {
		return this.performControllerRequest('PUT', {
			url: "/apps/"+ appId +"/release",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createAppFormation: function (appId, data) {
		return this.performControllerRequest('PUT', {
			url: "/apps/"+ appId +"/formations/"+ data.release,
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	getTaffyRelease: function () {
		return this.performControllerRequest('GET', {
			cacheResponse: true, // response doesn't change
			url: "/apps/taffy/release"
		});
	},

	createTaffyJob: function (data) {
		return this.performControllerRequest('POST', {
			url: "/apps/taffy/jobs",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},
});

})();
<commit_msg>dashboard: Fix waiting for deployment<commit_after>(function () {

"use strict";

Dashboard.Client = Marbles.Utils.createClass({
	displayName: "Client",

	mixins: [{
		ctor: {
			middleware: [
				Marbles.HTTP.Middleware.SerializeJSON,
				Marbles.HTTP.Middleware.WithCredentials
			]
		}
	}],

	willInitialize: function (endpoints) {
		this.endpoints = endpoints;
		this.__cachedResponses = {};
	},

	performRequest: function (method, args) {
		if ( !args.url ) {
				var err = new Error(this.constructor.displayName +".prototype.performRequest(): Can't make request without URL");
			setTimeout(function () {
				throw err;
			}.bind(this), 0);
			return Promise.reject(err);
		}

		var cacheResponse = args.cacheResponse;
		var cachedResponseKey = method + args.url;
		var cachedRepsonses = this.__cachedResponses;
		if (cacheResponse && cachedRepsonses[cachedResponseKey]) {
			return Promise.resolve(cachedRepsonses[cachedResponseKey]);
		}

		var middleware = args.middleware || [];
		delete args.middleware;

		return Marbles.HTTP(Marbles.Utils.extend({
			method: method,
			middleware: [].concat(this.constructor.middleware).concat(middleware),
		}, args)).then(function (args) {
			var res = args[0];
			var xhr = args[1];
			return new Promise(function (resolve, reject) {
				if (xhr.status >= 200 && xhr.status < 400) {
					if (cacheResponse) {
						cachedRepsonses[cachedResponseKey] = [res, xhr];
					}
					resolve([res, xhr]);
				} else {
					if (xhr.status === 401) {
						Dashboard.config.fetch();
					}

					reject([res, xhr]);
				}
			});
		});
	},

	performControllerRequest: function (method, args) {
		var controllerKey = (Dashboard.config.user || {}).controller_key;
		return this.performRequest(method, Marbles.Utils.extend({}, args, {
			middleware: [
				Marbles.HTTP.Middleware.BasicAuth("", controllerKey)
			],
			url: this.endpoints.cluster_controller + args.url
		}));
	},

	login: function (token) {
		return this.performRequest('POST', {
			url: this.endpoints.login,
			body: {
				token: token
			},
			headers: {
				'Content-Type': 'application/json'
			}
		}).then(function (args) {
			return Dashboard.config.fetch().then(function () {
				return args;
			});
		});
	},

	logout: function () {
		return this.performRequest('DELETE', {
			url: this.endpoints.logout
		}).then(function (args) {
			return Dashboard.config.fetch().then(function () {
				return args;
			});
		});
	},

	ping: function (endpoint, protocol) {
		endpoint = window.location.host.replace("dashboard", endpoint);
		endpoint = protocol + "://" + endpoint + "/ping";

		return this.performRequest('GET', {
			url: endpoint
		});
	},

	getApps: function () {
		return this.performControllerRequest('GET', {
			url: "/apps"
		});
	},

	getApp: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId)
		});
	},

	getAppRelease: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/release"
		});
	},

	getAppFormation: function (appId, releaseId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/formations/"+ encodeURIComponent(releaseId)
		});
	},

	getAppJobs: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/jobs"
		});
	},

	getAppRoutes: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/routes"
		});
	},

	getAppResources: function (appId) {
		return this.performControllerRequest('GET', {
			url: "/apps/"+ encodeURIComponent(appId) +"/resources"
		});
	},

	createAppRoute: function (appId, data) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ encodeURIComponent(appId) +"/routes",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deleteAppRoute: function (appId, routeType, routeId) {
		return this.performControllerRequest('DELETE', {
			url: "/apps/"+ appId +"/routes/"+ routeType +"/"+ routeId
		});
	},

	createApp: function (data) {
		return this.performControllerRequest('POST', {
			url: "/apps",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	updateApp: function (appId, data) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ encodeURIComponent(appId),
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deleteApp: function (appId) {
		return this.performControllerRequest('DELETE', {
			url: "/apps/"+ encodeURIComponent(appId)
		});
	},

	createAppDatabase: function (data) {
		return this.performControllerRequest('POST', {
			url: "/providers/postgres/resources",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createArtifact: function (data) {
		return this.performControllerRequest('POST', {
			url: "/artifacts",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createRelease: function (data) {
		return this.performControllerRequest('POST', {
			url: "/releases",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	deployAppRelease: function (appId, releaseId) {
		return this.performControllerRequest('POST', {
			url: "/apps/"+ appId +"/deploy",
			body: {id: releaseId},
			headers: {
				'Content-Type': 'application/json'
			}
		}).then(function (args) {
			var res = args[0];
			if (res.finished_at) {
				return args;
			}
			return this.waitForDeployment(appId, res.id).then(function () {
				return args;
			}, function (err) {
				return Promise.reject([err]);
			});
		}.bind(this));
	},

	waitForDeployment: function (appId, deploymentId) {
		if ( !window.hasOwnProperty('EventSource') ) {
			return Promise.reject('window.EventSource not defined');
		}

		var controllerKey = (Dashboard.config.user || {}).controller_key;
		var url = this.endpoints.cluster_controller +'/apps/'+ encodeURIComponent(appId) +'/events';
		url = url + Marbles.QueryParams.serializeParams([{
			key: controllerKey,
			object_type: 'deployment',
			object_id: deploymentId,
			past: 'true'
		}]);
		return new Promise(function (resolve, reject) {
			var es = new window.EventSource(url, {withCredentials: true});

			setTimeout(function () {
				reject("Timed out waiting for deployment completion");
				es.close();
			}, 10000);

			es.addEventListener("error", function (e) {
				reject(e);
				es.close();
			});
			es.addEventListener("complete", function () {
				resolve();
				es.close();
			});
			es.addEventListener("message", function (e) {
				var res = JSON.parse(e.data);
				if (res.data.status === "complete") {
					resolve();
					es.close();
				}
			});
		}.bind(this));
	},

	createAppRelease: function (appId, data) {
		return this.performControllerRequest('PUT', {
			url: "/apps/"+ appId +"/release",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	createAppFormation: function (appId, data) {
		return this.performControllerRequest('PUT', {
			url: "/apps/"+ appId +"/formations/"+ data.release,
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},

	getTaffyRelease: function () {
		return this.performControllerRequest('GET', {
			cacheResponse: true, // response doesn't change
			url: "/apps/taffy/release"
		});
	},

	createTaffyJob: function (data) {
		return this.performControllerRequest('POST', {
			url: "/apps/taffy/jobs",
			body: data,
			headers: {
				'Content-Type': 'application/json'
			}
		});
	},
});

})();
<|endoftext|>


<commit_before>/*
    Get all objects from a paginated resource.
*/
angular.module('getAll', []).factory('getAll', function() {
    var forEach = angular.forEach;
    var extend = angular.extend;

    var getAll = function(output_list, model, offset, params, success, error) {
        var items = model.get(extend({}, params, {offset: offset}, success, error), function() {
            forEach(items.objects, function(item) {
                output_list.push(new model(item));
            });

            if (items.meta.next !== null) {
                getAll(output_list, model, items.meta.offset + items.meta.limit, params, success, error);
            }
        });
    };

    return getAll;
});

/*
    Project model.
*/
angular.module('project', ['ngResource', 'getAll']).factory('Project', function($resource, getAll) {
    var Project = $resource('/api/v1/project/:id/', {'id': '@id'});

    Project.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Project, 0, params, success, error);
        return output_list;
    };

    return Project;
});

/*
    Worker model.
*/
angular.module('worker', ['ngResource', 'getAll', 'project']).factory('Worker', function($resource, getAll, Project) {
    var Worker = $resource('/api/v1/worker/:id/', {'id': '@id'});

    Worker.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Worker, 0, params, success);
        return output_list;
    };

    Worker.prototype.get_project = function() {
        if (!this._project && this.project) {
            this._project = Project.get({'id': this.project.split('/').splice(-2, 1)[0]});
        }
        return this._project;
    };

    return Worker;
});

/*
    Job template model.
*/
angular.module('jobTemplate', ['ngResource', 'getAll', 'worker']).factory('JobTemplate', function($resource, getAll, Worker) {
    var JobTemplate = $resource('/api/v1/job_template/:id/', {'id': '@id'});

    JobTemplate.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, JobTemplate, 0, params, success);
        return output_list;
    };

    JobTemplate.prototype.get_worker = function() {
        if (!this._worker && this.worker) {
            this._worker = Worker.get({'id': this.worker.split('/').splice(-2, 1)[0]});
        }
        return this._worker;
    };

    return JobTemplate;
});

/*
    Job model.
*/
angular.module('job', ['ngResource', 'getAll', 'jobTemplate']).factory('Job', function($resource, getAll, JobTemplate) {
    var Job = $resource('/api/v1/job/:id/', {'id': '@id'});

    Job.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Job, 0, params, success);
        return output_list;
    };

    Job.prototype.get_job_template = function() {
        if (!this._job_template && this.job_template) {
            this._job_template = JobTemplate.get({'id': this.job_template.split('/').splice(-2, 1)[0]});
        }
        return this._job_template;
    };

    Job.prototype.get_parent = function() {
        if (!this._parent && this.parent) {
            this._parent = Job.get({id: this.parent.split('/').splice(-2, 1)[0]});
        }
        return this._parent;
    };

    Job.prototype.get_children = function() {
        if (!this._children && this.children) {
            var children = [];
            this._children = children;
            angular.forEach(this.children, function(child_uri) {
                children.push(Job.get({id: child_uri.split('/').splice(-2, 1)[0]}));
            });
        }
        return this._children;
    };

    return Job;
});

/*
    Run model.
*/
angular.module('run', ['ngResource', 'getAll', 'job', 'jobrunner.services']).factory('Run', function($resource, getAll, Job, dtformat) {
    var Run = $resource('/api/v1/run/:id/', {'id': '@id'});

    Run.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Run, 0, params, success);
        return output_list;
    };

    Run.prototype.get_job = function() {
        if (!this._job && this.job) {
            this._job = Job.get({id:  this.job.split('/').splice(-2, 1)[0]});
        }
        return this._job;
    };

    Run.prototype.get_duration_string = function() {
        return dtformat.formatDuration(this.start_dts, this.return_dts);
    }

    return Run;
});
<commit_msg>Fix success and error callbacks.<commit_after>/*
    Get all objects from a paginated resource.
*/
angular.module('getAll', []).factory('getAll', function() {
    var forEach = angular.forEach;
    var extend = angular.extend;

    var getAll = function(output_list, model, offset, params, success, error) {
        var items = model.get(extend({}, params, {offset: offset}), function() {
            forEach(items.objects, function(item) {
                output_list.push(new model(item));
            });

            if (items.meta.next !== null) {
                getAll(output_list, model, items.meta.offset + items.meta.limit, params, success, error);
            } else {
                success();
            }
        }, error);
    };

    return getAll;
});

/*
    Project model.
*/
angular.module('project', ['ngResource', 'getAll']).factory('Project', function($resource, getAll) {
    var Project = $resource('/api/v1/project/:id/', {'id': '@id'});

    Project.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Project, 0, params, success, error);
        return output_list;
    };

    return Project;
});

/*
    Worker model.
*/
angular.module('worker', ['ngResource', 'getAll', 'project']).factory('Worker', function($resource, getAll, Project) {
    var Worker = $resource('/api/v1/worker/:id/', {'id': '@id'});

    Worker.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Worker, 0, params, success, error);
        return output_list;
    };

    Worker.prototype.get_project = function() {
        if (!this._project && this.project) {
            this._project = Project.get({'id': this.project.split('/').splice(-2, 1)[0]});
        }
        return this._project;
    };

    return Worker;
});

/*
    Job template model.
*/
angular.module('jobTemplate', ['ngResource', 'getAll', 'worker']).factory('JobTemplate', function($resource, getAll, Worker) {
    var JobTemplate = $resource('/api/v1/job_template/:id/', {'id': '@id'});

    JobTemplate.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, JobTemplate, 0, params, success, error);
        return output_list;
    };

    JobTemplate.prototype.get_worker = function() {
        if (!this._worker && this.worker) {
            this._worker = Worker.get({'id': this.worker.split('/').splice(-2, 1)[0]});
        }
        return this._worker;
    };

    return JobTemplate;
});

/*
    Job model.
*/
angular.module('job', ['ngResource', 'getAll', 'jobTemplate']).factory('Job', function($resource, getAll, JobTemplate) {
    var Job = $resource('/api/v1/job/:id/', {'id': '@id'});

    Job.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Job, 0, params, success);
        return output_list;
    };

    Job.prototype.get_job_template = function() {
        if (!this._job_template && this.job_template) {
            this._job_template = JobTemplate.get({'id': this.job_template.split('/').splice(-2, 1)[0]});
        }
        return this._job_template;
    };

    Job.prototype.get_parent = function() {
        if (!this._parent && this.parent) {
            this._parent = Job.get({id: this.parent.split('/').splice(-2, 1)[0]});
        }
        return this._parent;
    };

    Job.prototype.get_children = function() {
        if (!this._children && this.children) {
            var children = [];
            this._children = children;
            angular.forEach(this.children, function(child_uri) {
                children.push(Job.get({id: child_uri.split('/').splice(-2, 1)[0]}));
            });
        }
        return this._children;
    };

    return Job;
});

/*
    Run model.
*/
angular.module('run', ['ngResource', 'getAll', 'job', 'jobrunner.services']).factory('Run', function($resource, getAll, Job, dtformat) {
    var Run = $resource('/api/v1/run/:id/', {'id': '@id'});

    Run.all = function(params, success, error) {
        var output_list = [];
        getAll(output_list, Run, 0, params, success);
        return output_list;
    };

    Run.prototype.get_job = function() {
        if (!this._job && this.job) {
            this._job = Job.get({id:  this.job.split('/').splice(-2, 1)[0]});
        }
        return this._job;
    };

    Run.prototype.get_duration_string = function() {
        return dtformat.formatDuration(this.start_dts, this.return_dts);
    }

    return Run;
});
<|endoftext|>