<commit_before>/*
 * @author              Mitchell Field <mitchell.field@live.com.au>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

// Set browser action.
browser.browserAction.onClicked.addListener(onBrowserAction);

// Set default browser action title.
browser.browserAction.setTitle({
  title: browser.i18n.getMessage("browserAction_none_label")
});

// Listen to changes in storage.
browser.storage.onChanged.addListener(onStorageChanged);

const DISCARDABLE_TAB_URLS = new Set([
  "about:blank",
  "about:newtab",
  "about:privatebrowsing"
]);

const SORT_MODES = new Map([
  ["none", 0],
  ["host_title_path", 1],
  ["host_path_title", 2],
  ["title_host_path", 3]
]);

const DEFAULT_PREFS = {
  "pref_tabDeduplicate": "false",
  "pref_tabSortByParts": "none",
  "pref_tabSortBySearchParams": "true"
};

const PREFS = Object.assign(DEFAULT_PREFS);

const cullingWindows = new Set();
const sortingWindows = new Set();

const hostnameTokenCache = new Map();
const pathnameTokenCache = new Map();
const tabPropsCache = new Map();

/*
 * Called when the "browser action" is invoked.
 */
function onBrowserAction(tab, onClickData) {
  let sort = PREFS.pref_tabSortByParts !== "none";
  let deduplicate = PREFS.pref_tabDeduplicate === "true";

  if (!sort && !deduplicate) {

    // The browser action is not configured to sort nor deduplicate tabs.
    browser.runtime.openOptionsPage();
    return;
  }

  processTabs(tab.windowId, sort, deduplicate);
}

/*
 * Called when a storage area is changed.
 */
function onStorageChanged(changes, areaName) {
  if (areaName !== "sync" || !("preferences" in changes)) {
    return;
  }

  Object.assign(PREFS, changes.preferences.newValue);

  let sort = PREFS.pref_tabSortByParts !== "none";
  let deduplicate = PREFS.pref_tabDeduplicate === "true";

  // Default browser action title.
  let titleMessageID = "browserAction_none_label";

  if (sort) {
    if (deduplicate) {

      // Sort and deduplicate.
      titleMessageID = "browserAction_sort_deduplicate_label";
    } else {

      // Sort.
      titleMessageID = "browserAction_sort_label";
    }
  } else if (deduplicate) {

    // Deduplicate.
    titleMessageID = "browserAction_deduplicate_label";
  }

  // Set browser action title.
  browser.browserAction.setTitle({
    title: browser.i18n.getMessage(titleMessageID)
  });
}

/*
 * Sorts, deduplicates, and removes low-priority (or blank) tabs.
 *
 * @param windowId      window ID
 * @param sort          sort tabs
 * @param deduplicate   deduplicate tabs
 */
function processTabs(windowId, sort, deduplicate) {
  const gettingWindow = browser.windows.get(windowId, {
    populate: true
  });

  gettingWindow.then(windowInfo => {
    const gettingUnpinnedTabs = browser.tabs.query({
      pinned: false,
      windowId: windowInfo.id
    });

    gettingUnpinnedTabs.then(unpinnedTabs => {

      const windowId = windowInfo.id;

      // Check if window is already being sorted.
      if (sortingWindows.has(windowId))
        return;

      sortingWindows.add(windowId);

      // Initialise various caches for the window.

      hostnameTokenCache.set(windowId, new Map());
      pathnameTokenCache.set(windowId, new Map());
      tabPropsCache.set(windowId, new Map());

      // Get first tab index.
      const {index} = unpinnedTabs[0];

      // Get sorting order for tabs.
      unpinnedTabs.sort(compareTabs);

      let gettingMovedTabs;

      if (sort) {

        // Move tabs into place.
        gettingMovedTabs = browser.tabs.move(
          unpinnedTabs.map(tab => tab.id), {index: index});
      } else {

        // No-op.
        gettingMovedTabs = new Promise((resolve, reject) => {
          resolve(unpinnedTabs);
        });
      }

      gettingMovedTabs.then(tabsArray => {
        let tabProps;

        // Filter duplicate and discardable tabs.
        const unwantedTabs = tabsArray.filter(tab => {
          tabProps = getTabProps(tab);
          return tab.status === "complete" &&
            (tabProps.isDiscardable || deduplicate && tabProps.isDuplicate)
        });

        // Check for discardable tabs.
        const hasDiscardableTabs = tabsArray.some(tab => {
          tabProps = getTabProps(tab);
          return tab.status === "complete" && tabProps.isDiscardable;
        });

        let gettingRemovedTabs;

        if (hasDiscardableTabs) {

          // Create a new tab to remain after culling.
          const gettingNewTab = browser.tabs.create({
            active: false,
            windowId: windowId
          });

          // Remove tabs.
          gettingRemovedTabs = gettingNewTab.then(
            browser.tabs.remove(unwantedTabs.map(tab => tab.id)));
        } else {
          gettingRemovedTabs = browser.tabs.remove(
            unwantedTabs.map(tab => tab.id));
        }

        gettingRemovedTabs.then(removedTabs => {

          // Clear the window's caches.
          hostnameTokenCache.get(windowId).clear();
          pathnameTokenCache.get(windowId).clear();
          tabPropsCache.get(windowId).clear();

          // Allow the window's tabs to be sorted again.
          sortingWindows.delete(windowId);
        });
      });
    });
  });
}

/*
 * Gets certain properties of a tab.
 *
 * Tab properties are cached per-window for next access.
 *
 * @param tab           tab
 * @return              tab properties object
 */
function getTabProps(tab) {

  const {
    id: tabId,
    index: tabIndex,
    windowId
  } = tab;

  const tabPropsMap = tabPropsCache.get(windowId);
  let tabProps = tabPropsMap.get(tabId);

  if (tabProps)
    return tabProps;

  const {
    protocol = ':',
    hostname = '',
    pathname = '/',
    searchParams = '',
    hash = '#'
  } = new URL(tab.url || '');

  tabProps = {
    hostname,
    pathname,
    searchParams: searchParams.toString(),
    hash,
    hasAboutScheme: protocol === "about",
    hasPathname: pathname !== '/',
    title: tab.title || '',
    index: tabIndex,
    isDiscardable: DISCARDABLE_TAB_URLS.has(tab.url),
    isDuplicate: false
  };

  tabPropsMap.set(tabId, tabProps);

  return tabProps;
}

/*
 * Compares tabs based on certain criteria.
 *
 * @param tabA          first tab for comparison
 * @param tabB          second tab for comparison
 * @return              comparison numeric result
 */
function compareTabs(tabA, tabB) {

  const propsA = getTabProps(tabA);
  const propsB = getTabProps(tabB);

  const windowIdA = tabA.windowId;
  const windowIdB = tabB.windowId;

  // Map the string preference value to a number.
  let sortMode = SORT_MODES.get(PREFS.pref_tabSortByParts);

  let result;

  if ((result = propsA.hasAboutScheme - propsB.hasAboutScheme) !== 0)
    return result;

  if (sortMode === 3) { // title-host-path sorting. Compare titles.
    if ((result = propsA.title.localeCompare(propsB.title)) !== 0)
      return result;
  }

  // Split the hostname's TLD from its lower-level domains.
  const [tldA, lowerDomainTokensA] = splitHostname(propsA.hostname, windowIdA);
  const [tldB, lowerDomainTokensB] = splitHostname(propsB.hostname, windowIdB);

  // Compare hostnames.
  if ((result = compareTokens(lowerDomainTokensA, lowerDomainTokensB)) !== 0)
    return result;

  // Compare TLDs.
  if ((result = compareTokens(tldA, tldB)) !== 0)
    return result;

  // Compare pathlessness.
  if ((result = propsA.hasPathname - propsB.hasPathname) !== 0)
    return result;

  if (sortMode === 1) { // host-title-path sorting. Compare titles.
    if ((result = propsA.title.localeCompare(propsB.title)) !== 0)
      return result;
  }

  // Compare pathnames.
  result = compareTokens(splitPathname(propsA.pathname, windowIdA),
    splitPathname(propsB.pathname, windowIdB));

  if (result !== 0)
    return result;

  // Compare search parameters.
  if ((result = propsA.searchParams.localeCompare(propsB.searchParams)) !== 0) {
    return result;
  }

  // Compare hashes (fragments).
  if ((result = propsA.hash.localeCompare(propsB.hash)) !== 0)
    return result;

  if (sortMode === 2) { // host-path-title. Compare titles.
    if ((result = propsA.title.localeCompare(propsB.title)) !== 0)
      return result;
  }

  // The two tabs are considered duplicate. Mark the later tab as a duplicate.
  if (propsA.index < propsB.index) {
    propsB.isDuplicate = true;
  } else {
    propsA.isDuplicate = true;
  }

  return 0;
}

/*
 * Compares a list of tokens.
 *
 * Compare a token from one array to its corresponding token in the other array
 * until they sort differently or an array runs out of tokens to compare.
 *
 * @param tokensA       first array of tokens for comparison
 * @param tokensB       second array of tokens for comparison
 * @return              comparison numeric result
 */
function compareTokens(tokensA, tokensB) {

  const tokensALength = tokensA.length;
  const tokensBLength = tokensB.length;

  for (let tokenIndex = 0; ; ++tokenIndex) {
    const isEndedA = tokenIndex >= tokensALength; // End of 'A' tokens.
    const isEndedB = tokenIndex >= tokensBLength; // End of 'B' tokens.

    if (isEndedA || isEndedB)
      return isEndedB - isEndedA;

    const tokenA = tokensA[tokenIndex];
    const tokenB = tokensB[tokenIndex];
    const result = tokenA.localeCompare(tokenB);

    if (result !== 0)
      return result;
  }

  return 0;
}

/*
 * Roughly split hostname into top-level and lower-level domains.
 *
 * Will set/get cache entries by hostname and window ID.
 *
 * @param hostname      URL hostname
 * @param windowId      ID of associated window
 * @return              [top-level domain tokens, lower-level domain tokens]
 */
function splitHostname(hostname, windowId) {

  const tokensMap = hostnameTokenCache.get(windowId);
  let hostnameTokens = tokensMap.get(hostname);

  if (hostnameTokens)
    return hostnameTokens;

  const tokens = hostname.split('.').reverse();

  const tokensLength = tokens.length;
  let splitIndex;

  if (tokensLength > 2) {
    splitIndex = tokens[1].length <= 3 ? 2 : 1;
  } else {
    splitIndex = 1;
  }

  hostnameTokens = [tokens.slice(0, splitIndex), tokens.slice(splitIndex)];

  tokensMap.set(hostname, hostnameTokens);

  return hostnameTokens;
}

/*
 * Split pathname into tokens.
 *
 * Will set/get cache entries by pathname and window ID.
 *
 * @param hostname      URL pathname
 * @param windowId      ID of associated window
 * @return              [pathname tokens]
 */
function splitPathname(pathname, windowId) {

  const tokensMap = pathnameTokenCache.get(windowId);
  let pathnameTokens = tokensMap.get(pathname);

  if (pathnameTokens)
    return pathnameTokens;

  pathnameTokens = pathname.split('/');

  tokensMap.set(pathname, pathnameTokens);

  return pathnameTokens;
}
<commit_msg>Improve TabProps structure.<commit_after>/*
 * @author              Mitchell Field <mitchell.field@live.com.au>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

// Set browser action.
browser.browserAction.onClicked.addListener(onBrowserAction);

// Set default browser action title.
browser.browserAction.setTitle({
  title: browser.i18n.getMessage("browserAction_none_label")
});

// Listen to changes in storage.
browser.storage.onChanged.addListener(onStorageChanged);

const DISCARDABLE_TAB_URLS = new Set([
  "about:blank",
  "about:newtab",
  "about:privatebrowsing"
]);

const SORT_MODES = new Map([
  ["none", 0],
  ["host_title_path", 1],
  ["host_path_title", 2],
  ["title_host_path", 3]
]);

const DEFAULT_PREFS = {
  "pref_tabDeduplicate": "false",
  "pref_tabSortByParts": "none",
  "pref_tabSortBySearchParams": "true"
};

const PREFS = Object.assign(DEFAULT_PREFS);

const cullingWindows = new Set();
const sortingWindows = new Set();

const hostnameTokenCache = new Map();
const pathnameTokenCache = new Map();

function TabProps(tab) {
  const { id, index, status, title, url, windowId } = tab;

  const {
    protocol = ':',
    hostname = '',
    pathname = '/',
    searchParams = '',
    hash = '#'
  } = new URL(url || '');

  Object.assign(this, {
    _lowerDomainTokens: null,
    _pathnameTokens: null,
    _tldTokens: null,
    hasAboutScheme: protocol === "about",
    hasPathname: pathname !== '/',
    hash,
    hostname,
    id,
    index,
    isDiscardable: DISCARDABLE_TAB_URLS.has(url),
    isDuplicate: false,
    pathname,
    searchParams: searchParams.toString(),
    status,
    tab,
    title: title || '',
    url,
    windowId
  });
}

TabProps.prototype = {
  get lowerDomainTokens() {
    if (!this._lowerDomainTokens) {
      const hostnameTokens = splitHostname(this.hostname, this.windowId);
      this._tldTokens = hostnameTokens[0];
      this._lowerDomainTokens = hostnameTokens[1];
    }
    return this._lowerDomainTokens;
  },
  get pathnameTokens() {
    if (!this._pathnameTokens) {
      this._pathnameTokens = splitPathname(this.pathname, this.windowId);
    }
    return this._pathnameTokens;
  },
  get tldTokens() {
    if (!this._tldTokens) {
      const hostnameTokens = splitHostname(this.hostname, this.windowId);
      this._tldTokens = hostnameTokens[0];
      this._lowerDomainTokens = hostnameTokens[1];
    }
    return this._tldTokens;
  }
}

/*
 * Called when the "browser action" is invoked.
 */
function onBrowserAction(tab, onClickData) {
  let sort = PREFS.pref_tabSortByParts !== "none";
  let deduplicate = PREFS.pref_tabDeduplicate === "true";

  if (!sort && !deduplicate) {

    // The browser action is not configured to sort nor deduplicate tabs.
    browser.runtime.openOptionsPage();
    return;
  }

  processTabs(tab.windowId, sort, deduplicate);
}

/*
 * Called when a storage area is changed.
 */
function onStorageChanged(changes, areaName) {
  if (areaName !== "sync" || !("preferences" in changes)) {
    return;
  }

  Object.assign(PREFS, changes.preferences.newValue);

  let sort = PREFS.pref_tabSortByParts !== "none";
  let deduplicate = PREFS.pref_tabDeduplicate === "true";

  // Default browser action title.
  let titleMessageID = "browserAction_none_label";

  if (sort) {
    if (deduplicate) {

      // Sort and deduplicate.
      titleMessageID = "browserAction_sort_deduplicate_label";
    } else {

      // Sort.
      titleMessageID = "browserAction_sort_label";
    }
  } else if (deduplicate) {

    // Deduplicate.
    titleMessageID = "browserAction_deduplicate_label";
  }

  // Set browser action title.
  browser.browserAction.setTitle({
    title: browser.i18n.getMessage(titleMessageID)
  });
}

/*
 * Sorts, deduplicates, and removes low-priority (or blank) tabs.
 *
 * @param windowId      window ID
 * @param sort          sort tabs
 * @param deduplicate   deduplicate tabs
 */
function processTabs(windowId, sort, deduplicate) {
  const gettingWindow = browser.windows.get(windowId, {
    populate: true
  });

  gettingWindow.then(windowInfo => {
    const gettingUnpinnedTabs = browser.tabs.query({
      pinned: false,
      windowId,
    });

    gettingUnpinnedTabs.then(unpinnedTabs => {

      // Check if window is already being sorted.
      if (sortingWindows.has(windowId))
        return;

      sortingWindows.add(windowId);

      // Initialise various caches for the window.
      hostnameTokenCache.set(windowId, new Map());
      pathnameTokenCache.set(windowId, new Map());

      // Get first tab index.
      const {index} = unpinnedTabs[0];

      const tabPropsArray =
        unpinnedTabs.map(unpinnedTab => new TabProps(unpinnedTab));

      // Get sorting order for tabs.
      tabPropsArray.sort(compareTabs);

      let gettingMovedTabs;

      if (sort) {

        // Move tabs into place.
        gettingMovedTabs = browser.tabs.move(
          tabPropsArray.map(tabProps => tabProps.id), {index: index});
      } else {

        // No-op.
        gettingMovedTabs = new Promise((resolve, reject) => {
          resolve(unpinnedTabs);
        });
      }

      gettingMovedTabs.then(movedTabs => {

        // Filter duplicate and discardable tabs.
        const unwantedTabs = tabPropsArray.filter(tabProps =>
          tabProps.status === "complete" &&
            (tabProps.isDiscardable || deduplicate && tabProps.isDuplicate));

        // Check for discardable tabs.
        const hasDiscardableTabs = tabPropsArray.some(tabProps =>
          tabProps.status === "complete" && tabProps.isDiscardable);

        let gettingRemovedTabs;

        if (hasDiscardableTabs) {

          // Create a new tab to remain after culling.
          const gettingNewTab = browser.tabs.create({
            active: false,
            windowId: windowId
          });

          // Remove tabs.
          gettingRemovedTabs = gettingNewTab.then(
            browser.tabs.remove(unwantedTabs.map(tab => tab.id)));
        } else {
          gettingRemovedTabs = browser.tabs.remove(
            unwantedTabs.map(tab => tab.id));
        }

        gettingRemovedTabs.then(removedTabs => {

          // Clear the window's caches.
          hostnameTokenCache.get(windowId).clear();
          pathnameTokenCache.get(windowId).clear();

          // Allow the window's tabs to be sorted again.
          sortingWindows.delete(windowId);
        });
      });
    });
  });
}

/*
 * Compares tabs based on certain criteria.
 *
 * @param propsA        first tab properties for comparison
 * @param propsB        second tab properties for comparison
 * @return              comparison numeric result
 */
function compareTabs(propsA, propsB) {

  // Map the string preference value to a number.
  let sortMode = SORT_MODES.get(PREFS.pref_tabSortByParts);

  let result;

  if ((result = propsA.hasAboutScheme - propsB.hasAboutScheme) !== 0)
    return result;

  if (sortMode === 3) { // title-host-path sorting. Compare titles.
    if ((result = propsA.title.localeCompare(propsB.title)) !== 0)
      return result;
  }

  // Compare hostnames.
  if ((result =
    compareTokens(propsA.lowerDomainTokens, propsB.lowerDomainTokens)) !== 0)
    return result;

  // Compare TLDs.
  if ((result = compareTokens(propsA.tldTokens, propsB.tldTokens)) !== 0)
    return result;

  // Compare pathlessness.
  if ((result = propsA.hasPathname - propsB.hasPathname) !== 0)
    return result;

  if (sortMode === 1) { // host-title-path sorting. Compare titles.
    if ((result = propsA.title.localeCompare(propsB.title)) !== 0)
      return result;
  }

  // Compare pathnames.
  result = compareTokens(propsA.pathnameTokens, propsB.pathnameTokens);
  if (result !== 0)
    return result;

  // Compare search parameters.
  if ((result = propsA.searchParams.localeCompare(propsB.searchParams)) !== 0)
    return result;

  // Compare hashes (fragments).
  if ((result = propsA.hash.localeCompare(propsB.hash)) !== 0)
    return result;

  if (sortMode === 2) { // host-path-title. Compare titles.
    if ((result = propsA.title.localeCompare(propsB.title)) !== 0)
      return result;
  }

  // The two tabs are considered duplicate. Mark the later tab as a duplicate.
  if (propsA.index < propsB.index) {
    propsB.isDuplicate = true;
  } else {
    propsA.isDuplicate = true;
  }

  return 0;
}

/*
 * Compares a list of tokens.
 *
 * Compare a token from one array to its corresponding token in the other array
 * until they sort differently or an array runs out of tokens to compare.
 *
 * @param tokensA       first array of tokens for comparison
 * @param tokensB       second array of tokens for comparison
 * @return              comparison numeric result
 */
function compareTokens(tokensA, tokensB) {

  const tokensALength = tokensA.length;
  const tokensBLength = tokensB.length;

  for (let tokenIndex = 0; ; ++tokenIndex) {
    const isEndedA = tokenIndex >= tokensALength; // End of 'A' tokens.
    const isEndedB = tokenIndex >= tokensBLength; // End of 'B' tokens.

    if (isEndedA || isEndedB)
      return isEndedB - isEndedA;

    const tokenA = tokensA[tokenIndex];
    const tokenB = tokensB[tokenIndex];
    const result = tokenA.localeCompare(tokenB);

    if (result !== 0)
      return result;
  }

  return 0;
}

/*
 * Roughly split hostname into top-level and lower-level domains.
 *
 * Will set/get cache entries by hostname and window ID.
 *
 * @param hostname      URL hostname
 * @param windowId      ID of associated window
 * @return              [top-level domain tokens, lower-level domain tokens]
 */
function splitHostname(hostname, windowId) {

  const tokensMap = hostnameTokenCache.get(windowId);
  let hostnameTokens = tokensMap.get(hostname);

  if (hostnameTokens)
    return hostnameTokens;

  const tokens = hostname.split('.').reverse();

  const tokensLength = tokens.length;
  let splitIndex;

  if (tokensLength > 2) {
    splitIndex = tokens[1].length <= 3 ? 2 : 1;
  } else {
    splitIndex = 1;
  }

  hostnameTokens = [tokens.slice(0, splitIndex), tokens.slice(splitIndex)];

  tokensMap.set(hostname, hostnameTokens);

  return hostnameTokens;
}

/*
 * Split pathname into tokens.
 *
 * Will set/get cache entries by pathname and window ID.
 *
 * @param hostname      URL pathname
 * @param windowId      ID of associated window
 * @return              [pathname tokens]
 */
function splitPathname(pathname, windowId) {

  const tokensMap = pathnameTokenCache.get(windowId);
  let pathnameTokens = tokensMap.get(pathname);

  if (pathnameTokens)
    return pathnameTokens;

  pathnameTokens = pathname.split('/');

  tokensMap.set(pathname, pathnameTokens);

  return pathnameTokens;
}
<|endoftext|>


<commit_before>exports.create = function(_bird) {
	var self = Ti.UI.createWindow({
		fullscreen : false,
		locked : false
	});
	self.gmap = Ti.App.GMap.createView({
		userLocation : true,
		enableZoomControls : false,
		mapType : Ti.App.GMap.NORMAL_TYPE,
		userLocationButton : true,
		region : {
			latitude : 53.5,
			longitude : 10,
			latitudeDelta : 90,
			longitudeDelta : 40
		},
		animate : true
	});
	self.add(self.gmap);
	var lastregion = null;
	var lat, lng;
	var pins = [];
	var onregionchanged = function(_e) {
		if (self.locked == true) {
			lastregion = _e;
			console.log('locked');
			return;
		}
		self.locked = true;
		var box = (_e.latitude - _e.latitudeDelta / 2.1) + ',' + (_e.longitude - _e.longitudeDelta / 2.1) + ',' + (_e.latitude + _e.latitudeDelta / 2.1) + ',' + (_e.longitude + _e.longitudeDelta / 2.1);
		console.log('retrieving');
		Ti.App.XenoCanto.searchRecordings({
			box : box,
			onload : function(_data) {
				console.log('got ' + _data.recordings.length);
				;
				gmap.removeAllAnnotations();
				pins = [];
				for (var i = 0; i < _data.recordings.length && i < 99; i++) {
					pins.push(Ti.App.GMap.createAnnotation({
						latitude : _data.recordings[i].lat,
						title : _data.recordings[i].gen + ' ' + _data.recordings[i].sp,
						subtitle : _data.recordings[i].rec,
						pincolor : Ti.App.GMap.ANNOTATION_GREEN,
						longitude : _data.recordings[i].lng,
					}));
				}
				self.gmap.addAnnotations(pins);
				setTimeout(function() {
					console.log('unlocked');
					self.locked = false;
					if (lastregion)
						onregionchanged(lastregion);
					lastregion = null;
				}, 1000);

			}
		});
	}
	for (var i = 0; i < _bird.recordings.length; i++) {
		var r = _bird.recordings[i];
		if (r.lat != null && r.lng != null) {
			lat = r.lat;
			lng = r.lng;
			pins.push(Ti.App.GMap.createAnnotation({
				latitude : lat,
				title : r.loc,
				pincolor : Ti.App.GMap.ANNOTATION_GREEN,
				longitude : lng,
			}));
		}
	}
	setTimeout(function() {
		gmap.addAnnotations(pins);
		gmap.setLocation({
			latitude : lat,
			longitude : lng
		})
	}, 800);
	return self;
}
<commit_msg>deleting of comments<commit_after>exports.create = function(_bird) {
	var self = Ti.UI.createWindow({
		fullscreen : false,
		locked : false
	});
	self.gmap = Ti.App.GMap.createView({
		userLocation : true,
		enableZoomControls : false,
		mapType : Ti.App.GMap.NORMAL_TYPE,
		userLocationButton : true,
		region : {
			latitude : 53.5,
			longitude : 10,
			latitudeDelta : 90,
			longitudeDelta : 40
		},
		animate : true
	});
	self.add(self.gmap);
	var lastregion = null;
	var lat, lng;
	var pins = [];
	var onregionchanged = function(_e) {
		if (self.locked == true) {
			lastregion = _e;
			return;
		}
		self.locked = true;
		var box = (_e.latitude - _e.latitudeDelta / 2.1) + ',' + (_e.longitude - _e.longitudeDelta / 2.1) + ',' + (_e.latitude + _e.latitudeDelta / 2.1) + ',' + (_e.longitude + _e.longitudeDelta / 2.1);
		console.log('retrieving');
		Ti.App.XenoCanto.searchRecordings({
			box : box,
			onload : function(_data) {
				gmap.removeAllAnnotations();
				pins = [];
				for (var i = 0; i < _data.recordings.length && i < 99; i++) {
					pins.push(Ti.App.GMap.createAnnotation({
						latitude : _data.recordings[i].lat,
						title : _data.recordings[i].gen + ' ' + _data.recordings[i].sp,
						subtitle : _data.recordings[i].rec,
						pincolor : Ti.App.GMap.ANNOTATION_GREEN,
						longitude : _data.recordings[i].lng,
					}));
				}
				self.gmap.addAnnotations(pins);
				setTimeout(function() {
					console.log('unlocked');
					self.locked = false;
					if (lastregion)
						onregionchanged(lastregion);
					lastregion = null;
				}, 1000);
			}
		});
	}
	for (var i = 0; i < _bird.recordings.length; i++) {
		var r = _bird.recordings[i];
		if (r.lat != null && r.lng != null) {
			lat = r.lat;
			lng = r.lng;
			pins.push(Ti.App.GMap.createAnnotation({
				latitude : lat,
				title : r.loc,
				pincolor : Ti.App.GMap.ANNOTATION_GREEN,
				longitude : lng,
			}));
		}
	}
	setTimeout(function() {
		gmap.addAnnotations(pins);
		gmap.setLocation({
			latitude : lat,
			longitude : lng
		})
	}, 800);
	return self;
}
<|endoftext|>


<commit_before>odoo.define('home_page', function (require) {
    "use strict";

    var core = require('web.core');
    var Widget = require('web.Widget');
    var Model = require('web.Model');
    var session = require('web.session');
    var framework = require('web.framework');

    var QWeb = core.qweb;
    var _t = core._t;

    var HomePage = Widget.extend({
        events: {
            'click button[oe_top_link]': 'on_click_top',
            'click .oe_quick_link': 'on_click_quick',
            'click .oe_main_link': 'on_click_main',
        },
        on_click_top: function (e) {
            var self = this;
            e.preventDefault();
            var $button = $(e.currentTarget);
            var button_id = $button[0].id;
            var view_mode = _.contains((self.result_top[button_id])[1].split(','), 'tree') ? 'list' : 'form';
            var views = _.contains((self.result_top[button_id])[1].split(','), 'tree') ? [[self.result_top[button_id][5],
                'list'], [false, 'form']] : [[self.result_top[button_id][5], 'form']];
            var action={
                type: 'ir.actions.act_window',
                res_model: (self.result_top[button_id])[2],
                view_mode: view_mode,
                views: views,
                domain: self.result_top[button_id][3],
                context: self.result_top[button_id][4],
                name: self.result_top[button_id][6],
                target: self.result_top[button_id][7],
            }
            this.do_action(action, {clear_breadcrumbs: true});
        },
        on_click_quick: function (e) {
            var self  =this;
            var $a = $(e.currentTarget);
            var a_id = $a[0].id;
            var index_vals = result_quick[a_id][1];
            var view_mode = _.contains(index_vals[a_id][1].split(','), 'tree') ? 'list' : 'form';
            var views = _.contains(index_vals[a_id][1].split(','), 'tree') ? [[index_vals[a_id][5],
                'list'], [false, 'form']] : [[index_vals[a_id][5], 'form']];
            self.do_action({
                type: 'ir.actions.act_window',
                res_model: index_vals[a_id][2],
                view_mode: view_mode,
                name: index_vals[a_id][6],
                views: views,
                domain: index_vals[a_id][3],
                context: index_vals[a_id][4],
                target: index_vals[a_id][7],
            }, {
                clear_breadcrumbs: true,
            });
        },

        on_click_main: function (e) {
            var self  =this;
            var $a = $(e.currentTarget);
            var a_id = $a[0].id;
            var result_one = result_main[a_id];
            if (result_one[0] != result_one[1]) {
                var view_mode = _.contains((result_main[a_id])[1].split(','), 'tree') ? 'list' : 'form';
                var views = _.contains((result_main[a_id])[1].split(','), 'tree') ? [[result_main[a_id][6],
                    'list'], [false, 'form']] : [[result_main[a_id][6], 'form']];
                self.do_action({
                    type: 'ir.actions.act_window',
                    res_model: (result_main[a_id])[2],
                    view_mode: view_mode,
                    name: result_main[a_id][7],
                    view_type: result_main[a_id][1],
                    views: views,
                    domain: result_main[a_id][3],
                    context: result_main[a_id][5],
                    target: result_main[a_id][8],
                }, {
                    clear_breadcrumbs: true,
                });
            }
        },

        three_part_qweb_render: function () {
            var top_fist = $(QWeb.render('top_fist_1'));
            var top_second = $(QWeb.render('top_second_2'));
            var top_third = $(QWeb.render('top_third_3'));
            return {
                'top_fist': top_fist,
                'top_second': top_second,
                'top_third': top_third
            }
        },
        three_part_is_show: function (result, most_frame) {
            var self = this;
            if (result['top']) {
                self.$el.find('.main').append(most_frame.top_fist);
                this.result_top = result['top']
            }
            if (result['main']) {
                self.$el.find('.main').append(most_frame.top_second);
                var center_main_table = $(QWeb.render('center_main_table'));
                self.$el.find('.main_div').append(center_main_table);
                this.result_main = result['main']
            }
            if (result['right']) {
                self.$el.find('.main').append(most_frame.top_third);
                this.result_quick = result['right']
            }
        },
        second_part: function () {
            var self = this;
            var row_num = 0;
            var result_top = self.result_top;
            result_top.length / 4 > 1 ? row_num = 3 : row_num = parseInt(12 / result_top.length);
            for (var i = 0; i < result_top.length; i++) {
                var top_data = this.result_top[i][0].split('  ');
                if ((i + 1) / 4 > parseInt(result_top.length / 4)) {
                    row_num = parseInt(12 / (result_top.length % 4)) == 0 ? 4 : parseInt(12 / (result_top.length % 4))
                }
                 console.log(top_data);
                if (top_data.length == 2) {
                    var left_html_str = $("
\
                          <button class='btn btn-primary-outline btn-pill oe_top_link_" + i + "' oe_top_link='" + i + "' id='" + i + "' style='width: 160px;height: 160px;'>\

" + top_data[0] + "
\

" + top_data[1] + "
\
                          </button>

");
                    self.$el.find('.top_div').append(left_html_str);
                }
            }
        },
        thrid_part: function () {
            var index_last = 0;
            var self = this;
            var result_quick = self.result_quick;
            for (var i = 0; i < result_quick.length; i++) {
                var left_big_html_str = "
\

" + (result_quick[i][0].split(';'))[1] + "

"
                self.$el.find('.right_div').append(left_big_html_str);
                for (var j = 0; j < result_quick[i][1].length; j++) {
                    var left_html_str = $("
" +
                        "
" + result_quick[i][1][j][6] + "

");
                    self.$el.find('.right_small_div_' + i).append(left_html_str);
                    index_last++;
                }
            }
        },
        first_part: function () {
            var self = this;
            var index = 0;
            var row_num = 0;
            var result_main = self.result_main;
            result_main.length / 4 > 1 ? row_num = 3 : row_num = parseInt(12 / result_main.length);
            for (var j = 0; j < result_main.length; j++) {
                var result_one = result_main[index];
                var center_html_str = "

\

" + result_one[0] + "
\

";
                self.$el.find('.feature-list').append(center_html_str);
                index++;
            }
        },
        start: function () {
            var num = this.get('value');
            var self = this;
            self.$el.append("

")
            /*首页分为三块  样式进行显示 分别是 数据统计  业务总览 实时报表 */
            var most_frame = this.three_part_qweb_render();
            new Model("home.page").call("get_action_url").then(function (result) {
                var index = 0;
                /* 三块 可以选择性的不显示某个 模块 */
                self.three_part_is_show(result, most_frame);
                /* 第一块的视图的构建 及跳转的逻辑 */
                self.first_part();
                /* 第er块的视图的构建 及跳转的逻辑 */
                self.second_part();
                /* 第san块的视图的构建 及跳转的逻辑 */
                self.thrid_part()
            });
        },
    });
    core.action_registry.add('home_page.homepage', HomePage);
})<commit_msg>参数的设置失误，测试不全，（换了好几种方式没想到更好的）<commit_after>odoo.define('home_page', function (require) {
    "use strict";

    var core = require('web.core');
    var Widget = require('web.Widget');
    var Model = require('web.Model');
    var session = require('web.session');
    var framework = require('web.framework');

    var QWeb = core.qweb;
    var _t = core._t;

    var HomePage = Widget.extend({
        events: {
            'click button[oe_top_link]': 'on_click_top',
            'click .oe_quick_link': 'on_click_quick',
            'click .oe_main_link': 'on_click_main',
        },
        on_click_top: function (e) {
            var self = this;
            e.preventDefault();
            var $button = $(e.currentTarget);
            var button_id = $button[0].id;
            var view_mode = _.contains((self.result_top[button_id])[1].split(','), 'tree') ? 'list' : 'form';
            var views = _.contains((self.result_top[button_id])[1].split(','), 'tree') ? [[self.result_top[button_id][5],
                'list'], [false, 'form']] : [[self.result_top[button_id][5], 'form']];
            var action={
                type: 'ir.actions.act_window',
                res_model: (self.result_top[button_id])[2],
                view_mode: view_mode,
                views: views,
                domain: self.result_top[button_id][3],
                context: self.result_top[button_id][4],
                name: self.result_top[button_id][6],
                target: self.result_top[button_id][7],
            }
            this.do_action(action, {clear_breadcrumbs: true});
        },
        on_click_quick: function (e) {
            var self  =this;
            var $a = $(e.currentTarget);
            var a_id = $a[0].id;
            var index_vals = self.result_quick[a_id][1];
            var view_mode = _.contains(index_vals[a_id][1].split(','), 'tree') ? 'list' : 'form';
            var views = _.contains(index_vals[a_id][1].split(','), 'tree') ? [[index_vals[a_id][5],
                'list'], [false, 'form']] : [[index_vals[a_id][5], 'form']];
            self.do_action({
                type: 'ir.actions.act_window',
                res_model: index_vals[a_id][2],
                view_mode: view_mode,
                name: index_vals[a_id][6],
                views: views,
                domain: index_vals[a_id][3],
                context: index_vals[a_id][4],
                target: index_vals[a_id][7],
            }, {
                clear_breadcrumbs: true,
            });
        },

        on_click_main: function (e) {
            var self  =this;
            var $a = $(e.currentTarget);
            var a_id = $a[0].id;
            var result_main = self.result_main;
            var result_one = result_main[a_id];
            if (result_one[0] != result_one[1]) {
                var view_mode = _.contains((result_main[a_id])[1].split(','), 'tree') ? 'list' : 'form';
                var views = _.contains((result_main[a_id])[1].split(','), 'tree') ? [[result_main[a_id][6],
                    'list'], [false, 'form']] : [[result_main[a_id][6], 'form']];
                self.do_action({
                    type: 'ir.actions.act_window',
                    res_model: (result_main[a_id])[2],
                    view_mode: view_mode,
                    name: result_main[a_id][7],
                    view_type: result_main[a_id][1],
                    views: views,
                    domain: result_main[a_id][3],
                    context: result_main[a_id][5],
                    target: result_main[a_id][8],
                }, {
                    clear_breadcrumbs: true,
                });
            }
        },

        three_part_qweb_render: function () {
            var top_fist = $(QWeb.render('top_fist_1'));
            var top_second = $(QWeb.render('top_second_2'));
            var top_third = $(QWeb.render('top_third_3'));
            return {
                'top_fist': top_fist,
                'top_second': top_second,
                'top_third': top_third
            }
        },
        three_part_is_show: function (result, most_frame) {
            var self = this;
            if (result['top']) {
                self.$el.find('.main').append(most_frame.top_fist);
                this.result_top = result['top']
            }
            if (result['main']) {
                self.$el.find('.main').append(most_frame.top_second);
                var center_main_table = $(QWeb.render('center_main_table'));
                self.$el.find('.main_div').append(center_main_table);
                this.result_main = result['main']
            }
            if (result['right']) {
                self.$el.find('.main').append(most_frame.top_third);
                this.result_quick = result['right']
            }
        },
        second_part: function () {
            var self = this;
            var row_num = 0;
            var result_top = self.result_top;
            result_top.length / 4 > 1 ? row_num = 3 : row_num = parseInt(12 / result_top.length);
            for (var i = 0; i < result_top.length; i++) {
                var top_data = this.result_top[i][0].split('  ');
                if ((i + 1) / 4 > parseInt(result_top.length / 4)) {
                    row_num = parseInt(12 / (result_top.length % 4)) == 0 ? 4 : parseInt(12 / (result_top.length % 4))
                }
                 console.log(top_data);
                if (top_data.length == 2) {
                    var left_html_str = $("
\
                          <button class='btn btn-primary-outline btn-pill oe_top_link_" + i + "' oe_top_link='" + i + "' id='" + i + "' style='width: 160px;height: 160px;'>\

" + top_data[0] + "
\

" + top_data[1] + "
\
                          </button>

");
                    self.$el.find('.top_div').append(left_html_str);
                }
            }
        },
        thrid_part: function () {
            var index_last = 0;
            var self = this;
            var result_quick = self.result_quick;
            for (var i = 0; i < result_quick.length; i++) {
                var left_big_html_str = "
\

" + (result_quick[i][0].split(';'))[1] + "

"
                self.$el.find('.right_div').append(left_big_html_str);
                for (var j = 0; j < result_quick[i][1].length; j++) {
                    var left_html_str = $("
" +
                        "
" + result_quick[i][1][j][6] + "

");
                    self.$el.find('.right_small_div_' + i).append(left_html_str);
                    index_last++;
                }
            }
        },
        first_part: function () {
            var self = this;
            var index = 0;
            var row_num = 0;
            var result_main = self.result_main;
            result_main.length / 4 > 1 ? row_num = 3 : row_num = parseInt(12 / result_main.length);
            for (var j = 0; j < result_main.length; j++) {
                var result_one = result_main[index];
                var center_html_str = "

\

" + result_one[0] + "
\

";
                self.$el.find('.feature-list').append(center_html_str);
                index++;
            }
        },
        start: function () {
            var num = this.get('value');
            var self = this;
            self.$el.append("

")
            /*首页分为三块  样式进行显示 分别是 数据统计  业务总览 实时报表 */
            var most_frame = this.three_part_qweb_render();
            new Model("home.page").call("get_action_url").then(function (result) {
                var index = 0;
                /* 三块 可以选择性的不显示某个 模块 */
                self.three_part_is_show(result, most_frame);
                /* 第一块的视图的构建 及跳转的逻辑 */
                self.first_part();
                /* 第er块的视图的构建 及跳转的逻辑 */
                self.second_part();
                /* 第san块的视图的构建 及跳转的逻辑 */
                self.thrid_part()
            });
        },
    });
    core.action_registry.add('home_page.homepage', HomePage);
})<|endoftext|>


<commit_before>const dockerImages = require('../lib/image-mgr.js'),
	utils = require('./utils.js')

//Get image names
dockerImages.getNames(function(err, json) {
	utils.print(err,json)
}, {onlytagged:true})

//Get detailed list of images in javascript object
dockerImages.getDetailedList(function(err, data) {
	utils.print(err,data)

})

//Get json list of images
dockerImages.getJSONList(function(err, data) {
	utils.print(err, data)
})

<commit_msg>Update<commit_after>const dockerImages = require('../lib/image-mgr.js'),
	utils = require('./utils.js')

//Get image names
//dockerImages.getNames(function(err, json) {
//	utils.print(err,json)
//}, {onlytagged:true})
//
//
//Get detailed list of images in javascript object
dockerImages.getDetailedList(function(err, data) {
	utils.print(err,data)

})

//Get json list of images
dockerImages.getJSONList(function(err, data) {
	utils.print(err, data)
})

<|endoftext|>


<commit_before>import Promise from 'bluebird';
import config from 'config';
import debugLib from 'debug';
import { filter, groupBy, keyBy, pick, sumBy } from 'lodash';
import moment from 'moment';

import MemberRoles from '../server/constants/roles.ts';
import emailLib from '../server/lib/email';
import { getBackersStats, getHostedCollectives, sumTransactions } from '../server/lib/hostlib';
import { stripHTML } from '../server/lib/sanitize-html';
import { getTransactions } from '../server/lib/transactions';
import { exportToPDF } from '../server/lib/utils';
import models, { Op, sequelize } from '../server/models';

const debug = debugLib('hostreport');
const sumByWhen = (vector, iteratee, predicate) => sumBy(filter(vector, predicate), iteratee);

const summary = {
  totalHosts: 0,
  totalActiveHosts: 0,
  totalCollectives: 0,
  totalActiveCollectives: 0,
  numberTransactions: 0,
  numberDonations: 0,
  numberPaidExpenses: 0,
  hosts: [],
};

async function HostReport(year, month, hostId) {
  let startDate, endDate;

  const d = new Date();
  d.setFullYear(year);
  let yearlyReport = false;
  if (typeof month === 'number') {
    d.setMonth(month);
    startDate = new Date(d.getFullYear(), d.getMonth(), 1);
    endDate = new Date(d.getFullYear(), d.getMonth() + 1, 1);
  } else {
    // yearly report
    month = '';
    yearlyReport = true;
    startDate = new Date(d.getFullYear(), 0, 1);
    endDate = new Date(d.getFullYear() + 1, 0, 1);
  }

  const endDateIncluded = moment(endDate).subtract(1, 'days').toDate();

  const dateRange = {
    createdAt: { [Op.gte]: startDate, [Op.lt]: endDate },
  };

  const emailTemplate = yearlyReport ? 'host.yearlyreport' : 'host.monthlyreport'; // NOTE: this will be later converted to 'host.report'
  const reportName = yearlyReport ? `${year} Yearly Host Report` : `${year}/${month + 1} Monthly Host Report`;
  const dateFormat = yearlyReport ? 'YYYY' : 'YYYYMM';
  const csvFilename = `${moment(d).format(dateFormat)}-transactions.csv`;
  const pdfFilename = `${moment(d).format(dateFormat)}-expenses.pdf`;
  console.log('startDate', startDate, 'endDate', endDate);

  year = year || startDate.getFullYear();

  let previewCondition = '';
  if (process.env.DEBUG && process.env.DEBUG.match(/preview/)) {
    previewCondition = 'AND c.id IN (11004, 9804, 9802, 9801)';
  } // open source collective host, wwcode host, brusselstogether, changex
  // previewCondition = "AND c.id IN (9802)"; // brusselstogether

  if (hostId) {
    previewCondition = `AND c.id = ${hostId}`;
  }

  if (process.env.SLUGS) {
    const slugs = process.env.SLUGS.split(',');
    previewCondition = `AND c.slug IN ('${slugs.join("','")}')`;
  }
  if (process.env.SKIP_SLUGS) {
    const slugs = process.env.SKIP_SLUGS.split(',');
    previewCondition = `AND c.slug NOT IN ('${slugs.join("','")}')`;
  }

  const getHostStats = async (host, collectiveids) => {
    // Since collectives can change host,
    // we don't fetch transactions based on the CollectiveId but based on the HostCollectiveId
    // at the time of the transaction
    const where = { HostCollectiveId: host.id };
    const whereWithDateRange = { ...where, ...dateRange };

    return {
      balance: await sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...where, createdAt: { [Op.lt]: endDate } } },
        host.currency,
      ), // total host balance
      totalMoneyManaged:
        (
          await sumTransactions(
            'amountInHostCurrency',
            { where: { ...where, createdAt: { [Op.lt]: endDate } } },
            host.currency,
          )
        ).totalInHostCurrency +
        (
          await sumTransactions(
            'paymentProcessorFeeInHostCurrency',
            { where: { ...where, createdAt: { [Op.lt]: endDate } } },
            host.currency,
          )
        ).totalInHostCurrency +
        (
          await sumTransactions(
            'platformFeeInHostCurrency',
            { where: { ...where, createdAt: { [Op.lt]: endDate } } },
            host.currency,
          )
        ).totalInHostCurrency,
      delta: await sumTransactions('netAmountInCollectiveCurrency', { where: whereWithDateRange }, host.currency), // delta host balance last month
      backers: await getBackersStats(startDate, endDate, collectiveids),
    };
  };

  const processHost = async host => {
    try {
      summary.totalHosts++;
      console.log('>>> Processing host', host.slug);
      const data = {},
        attachments = [];
      const note = 'using fxrate of the day of the transaction as provided by the ECB. Your effective fxrate may vary.';
      const expensesPerPage = 30; // number of expenses per page of the Table Of Content (for PDF export)

      let collectivesById = {};
      let page = 1;
      let currentPage = 0;

      data.host = host.info;
      data.collective = host.info;
      data.reportDate = endDate;
      data.reportName = reportName;
      data.month = !yearlyReport && moment(startDate).format('MMMM');
      data.year = year;
      data.startDate = startDate;
      data.endDate = endDate;
      data.endDateIncluded = endDateIncluded;
      data.config = pick(config, 'host');
      data.maxSlugSize = 0;
      data.notes = null;
      data.expensesPerPage = [[]];
      data.taxType = host.getTaxType() || 'Taxes';
      data.stats = {};

      const getHostAdminsEmails = host => {
        if (host.type === 'USER') {
          return models.User.findAll({ where: { CollectiveId: host.id } }).map(u => u.email);
        }
        return models.Member.findAll({
          where: {
            CollectiveId: host.id,
            role: { [Op.or]: [MemberRoles.ADMIN, MemberRoles.ACCOUNTANT] },
          },
        }).map(
          admin => {
            return models.User.findOne({
              attributes: ['email'],
              where: { CollectiveId: admin.MemberCollectiveId },
            }).then(user => user.email);
          },
          { concurrency: 1 },
        );
      };

      const processTransaction = async transaction => {
        const t = {
          ...transaction.info,
          Expense: transaction.Expense
            ? {
                ...transaction.Expense.info,
                items: transaction.Expense.items.map(item => item.dataValues),
                PayoutMethod: transaction.Expense.PayoutMethod,
              }
            : null,
        };
        t.collective = collectivesById[t.CollectiveId].dataValues;
        t.collective.shortSlug = t.collective.slug.replace(/^wwcode-?(.)/, '$1');
        t.notes = t.Expense && t.Expense.privateMessage && stripHTML(t.Expense.privateMessage);
        if (t.data && t.data.fxrateSource) {
          t.notes = t.notes ? `${t.notes} (${note})` : note;
          data.notes = note;
        }
        t.source = t.ExpenseId ? 'EXPENSE' : t.OrderId ? 'ORDER' : 'OTHER';

        // We prepare expenses for the PDF export
        if (t.type === 'DEBIT' && t.ExpenseId) {
          t.page = page++;
          if ((page - 1) % expensesPerPage === 0) {
            currentPage++;
            data.expensesPerPage[currentPage] = [];
          }
          data.expensesPerPage[currentPage].push(t);
        }

        data.maxSlugSize = Math.max(data.maxSlugSize, t.collective.shortSlug.length + 1);
        if (!t.description) {
          const source = await transaction.getSource();
          t.description = source.description;
        }
        return t;
      };

      const collectives = await getHostedCollectives(host.id, startDate, endDate);
      collectivesById = keyBy(collectives, 'id');
      data.stats.totalCollectives = collectives.filter(c => c.type === 'COLLECTIVE').length;
      summary.totalCollectives += data.stats.totalCollectives;
      console.log(`>>> processing ${data.stats.totalCollectives} collectives`);
      let transactions = await getTransactions(Object.keys(collectivesById), startDate, endDate, {
        where: { HostCollectiveId: host.id },
        include: [
          {
            model: models.Expense,
            include: [
              'fromCollective',
              {
                model: models.ExpenseItem,
                as: 'items',
                where: {
                  url: { [Op.not]: null },
                },
              },
              { model: models.PayoutMethod, attributes: ['type'] },
            ],
          },
          {
            model: models.User,
            as: 'createdByUser',
          },
          { model: models.PaymentMethod, attributes: ['service', 'type'] },
        ],
      });

      if (!transactions || transactions.length == 0) {
        throw new Error('No transaction found');
      }
      console.log(`>>> processing ${transactions.length} transactions`);
      transactions = await Promise.all(transactions.map(processTransaction));
      const csv = models.Transaction.exportCSV(transactions, collectivesById);
      attachments.push({
        filename: `${host.slug}-${csvFilename}`,
        content: csv,
      });
      data.transactions = transactions;
      // Don't generate PDF in email if it's the yearly report
      let pdf;
      if (yearlyReport || process.env.SKIP_PDF) {
      } else {
        pdf = await exportToPDF('expenses', data, {
          paper: host.currency === 'USD' ? 'Letter' : 'A4',
        }).catch(error => {
          console.error(error);
          return;
        });
      }

      // Mailgun limit is 25MB
      if (pdf && pdf.length < 24000000) {
        attachments.push({
          filename: `${host.slug}-${pdfFilename}`,
          content: pdf,
        });
        data.expensesPdf = true;
      }
      const stats = await getHostStats(host, Object.keys(collectivesById));

      const { donations, expenses, otherCredits, otherDebits } = groupBy(data.transactions, t => {
        if (t.OrderId && t.type === 'CREDIT') {
          return 'donations';
        } else if (t.ExpenseId && t.type === 'DEBIT') {
          return 'expenses';
          // TODO REPLACE WITH OTHER INCOMES AND OTHER EXPENSES
        } else if (t.type === 'DEBIT') {
          return 'otherDebits';
        } else if (t.type === 'CREDIT') {
          return 'otherCredits';
        }
      });

      const totalAmountDonations = sumBy(donations, 'amountInHostCurrency');
      const paymentProcessorFees = sumBy(donations, 'paymentProcessorFeeInHostCurrency');
      const platformFees = sumBy(donations, 'platformFeeInHostCurrency');
      const totalAmountOtherCredits = sumBy(otherCredits, 'amountInHostCurrency');
      const paymentProcessorFeesOtherCredits = sumBy(otherCredits, 'paymentProcessorFeeInHostCurrency');
      const platformFeesOtherCredits = sumBy(otherCredits, 'platformFeeInHostCurrency');
      const totalAmountOtherDebits = sumBy(otherDebits, 'amountInHostCurrency');
      const paymentProcessorFeesOtherDebits = sumBy(otherDebits, 'paymentProcessorFeeInHostCurrency');
      const platformFeesOtherDebits = sumBy(otherDebits, 'platformFeeInHostCurrency');
      const payoutProcessorFeesPaypal = sumByWhen(
        expenses,
        'paymentProcessorFeeInHostCurrency',
        t => t.Expense?.PayoutMethod?.type === 'PAYPAL',
      );
      const payoutProcessorFeesTransferWise = sumByWhen(
        expenses,
        'paymentProcessorFeeInHostCurrency',
        t => t.Expense?.PayoutMethod?.type === 'BANK_ACCOUNT',
      );
      const payoutProcessorFeesOther = sumByWhen(
        expenses,
        'paymentProcessorFeeInHostCurrency',
        t => (t.Expense && !t.Expense.PayoutMethod) || t.Expense?.PayoutMethod?.type === 'OTHER',
      );
      const totalNetAmountReceived =
        totalAmountDonations +
        paymentProcessorFees +
        platformFees +
        totalAmountOtherCredits +
        paymentProcessorFeesOtherCredits +
        platformFeesOtherCredits;
      const totalTaxAmountCollected = sumByWhen(transactions, 'taxAmount', t => t.type === 'CREDIT');
      const totalAmountPaidExpenses = sumByWhen(expenses, 'netAmountInHostCurrency');
      const totalHostFees = sumBy([...donations, ...otherCredits], 'hostFeeInHostCurrency');
      const totalNetAmountReceivedForCollectives = sumBy([...donations, ...otherCredits], 'netAmountInHostCurrency');
      const totalAmountSpent =
        totalAmountPaidExpenses +
        payoutProcessorFeesOther +
        payoutProcessorFeesPaypal +
        totalAmountOtherDebits +
        paymentProcessorFeesOtherDebits +
        platformFeesOtherDebits;

      data.stats = {
        ...data.stats,
        ...stats,
        numberDonations: donations.length,
        numberOtherCredits: otherCredits?.length || 0,
        numberOtherDebits: otherDebits?.length || 0,
        numberPaidExpenses: expenses.length,
        numberTransactions: transactions.length,
        paymentProcessorFees,
        paymentProcessorFeesOtherCredits,
        paymentProcessorFeesOtherDebits,
        payoutProcessorFeesOther,
        payoutProcessorFeesPaypal,
        payoutProcessorFeesTransferWise,
        platformFees,
        platformFeesOtherCredits,
        platformFeesOtherDebits,
        totalActiveCollectives: Object.keys(keyBy(data.transactions, 'CollectiveId')).length,
        totalAmountDonations,
        totalAmountOtherCredits,
        totalAmountOtherDebits,
        totalAmountPaidExpenses,
        totalAmountSpent,
        totalHostFees,
        totalNetAmountReceived,
        totalNetAmountReceivedForCollectives,
        totalTaxAmountCollected,
      };

      summary.hosts.push({
        host: { name: host.name, slug: host.slug, currency: host.currency },
        stats: data.stats,
      });
      summary.totalActiveHosts++;
      summary.totalActiveCollectives += data.stats.totalActiveCollectives;
      summary.numberTransactions += data.stats.numberTransactions;
      summary.numberDonations += data.stats.numberDonations;
      summary.numberPaidExpenses += data.stats.numberPaidExpenses;
      summary.totalAmountPaidExpenses += data.stats.totalAmountPaidExpenses;
      // Don't send transactions in email if there is more than 1000
      if (data.transactions.length > 1000) {
        delete data.transactions;
      }
      const admins = await getHostAdminsEmails(host);
      await sendEmail(admins, data, attachments);
    } catch (e) {
      console.error(`Error in processing host ${host.slug}:`, e);
      debug(e);
    }
  };

  const sendEmail = (recipients, data, attachments) => {
    debug('Sending email to ', recipients);
    if (!recipients || recipients.length === 0) {
      console.error('Unable to send host report for ', data.host.slug, 'No recipient to send to');
      return;
    }
    debug('email data stats', JSON.stringify(data.stats, null, 2));
    const options = { attachments };
    return emailLib.send(emailTemplate, recipients, data, options);
  };

  const query = `
  with "hosts" as (SELECT DISTINCT "HostCollectiveId" AS id FROM "Collectives" WHERE "deletedAt" IS NULL AND "isActive" IS TRUE AND "HostCollectiveId" IS NOT NULL)
  SELECT c.* FROM "Collectives" c WHERE c.id IN (SELECT h.id FROM hosts h) ${previewCondition}
  `;

  const hosts = await sequelize.query(query, {
    model: models.Collective,
    type: sequelize.QueryTypes.SELECT,
  });
  console.log(`Preparing the ${reportName} for ${hosts.length} hosts`);

  return Promise.map(hosts, processHost, { concurrency: 1 }).then(() => {
    console.log('>>> All done. Exiting.');
    process.exit(0);
  });
}

export default HostReport;
<commit_msg>Update host-report.js<commit_after>import Promise from 'bluebird';
import config from 'config';
import debugLib from 'debug';
import { filter, groupBy, keyBy, pick, sumBy } from 'lodash';
import moment from 'moment';

import MemberRoles from '../server/constants/roles.ts';
import emailLib from '../server/lib/email';
import { getBackersStats, getHostedCollectives, sumTransactions } from '../server/lib/hostlib';
import { stripHTML } from '../server/lib/sanitize-html';
import { getTransactions } from '../server/lib/transactions';
import { exportToPDF } from '../server/lib/utils';
import models, { Op, sequelize } from '../server/models';

const debug = debugLib('hostreport');
const sumByWhen = (vector, iteratee, predicate) => sumBy(filter(vector, predicate), iteratee);

const summary = {
  totalHosts: 0,
  totalActiveHosts: 0,
  totalCollectives: 0,
  totalActiveCollectives: 0,
  numberTransactions: 0,
  numberDonations: 0,
  numberPaidExpenses: 0,
  hosts: [],
};

async function HostReport(year, month, hostId) {
  let startDate, endDate;

  const d = new Date();
  d.setFullYear(year);
  let yearlyReport = false;
  if (typeof month === 'number') {
    d.setMonth(month);
    startDate = new Date(d.getFullYear(), d.getMonth(), 1);
    endDate = new Date(d.getFullYear(), d.getMonth() + 1, 1);
  } else {
    // yearly report
    month = '';
    yearlyReport = true;
    startDate = new Date(d.getFullYear(), 0, 1);
    endDate = new Date(d.getFullYear() + 1, 0, 1);
  }

  const endDateIncluded = moment(endDate).subtract(1, 'days').toDate();

  const dateRange = {
    createdAt: { [Op.gte]: startDate, [Op.lt]: endDate },
  };

  const emailTemplate = yearlyReport ? 'host.yearlyreport' : 'host.monthlyreport'; // NOTE: this will be later converted to 'host.report'
  const reportName = yearlyReport ? `${year} Yearly Host Report` : `${year}/${month + 1} Monthly Host Report`;
  const dateFormat = yearlyReport ? 'YYYY' : 'YYYYMM';
  const csvFilename = `${moment(d).format(dateFormat)}-transactions.csv`;
  const pdfFilename = `${moment(d).format(dateFormat)}-expenses.pdf`;
  console.log('startDate', startDate, 'endDate', endDate);

  year = year || startDate.getFullYear();

  let previewCondition = '';
  if (process.env.DEBUG && process.env.DEBUG.match(/preview/)) {
    previewCondition = 'AND c.id IN (11004, 9804, 9802, 9801)';
  } // open source collective host, wwcode host, brusselstogether, changex
  // previewCondition = "AND c.id IN (9802)"; // brusselstogether

  if (hostId) {
    previewCondition = `AND c.id = ${hostId}`;
  }

  if (process.env.SLUGS) {
    const slugs = process.env.SLUGS.split(',');
    previewCondition = `AND c.slug IN ('${slugs.join("','")}')`;
  }
  if (process.env.SKIP_SLUGS) {
    const slugs = process.env.SKIP_SLUGS.split(',');
    previewCondition = `AND c.slug NOT IN ('${slugs.join("','")}')`;
  }

  const getHostStats = async (host, collectiveids) => {
    // Since collectives can change host,
    // we don't fetch transactions based on the CollectiveId but based on the HostCollectiveId
    // at the time of the transaction
    const where = { HostCollectiveId: host.id };
    const whereWithDateRange = { ...where, ...dateRange };

    return {
      balance: await sumTransactions(
        'netAmountInCollectiveCurrency',
        { where: { ...where, createdAt: { [Op.lt]: endDate } } },
        host.currency,
      ), // total host balance
      totalMoneyManaged:
        (
          await sumTransactions(
            'amountInHostCurrency',
            { where: { ...where, createdAt: { [Op.lt]: endDate } } },
            host.currency,
          )
        ).totalInHostCurrency +
        (
          await sumTransactions(
            'paymentProcessorFeeInHostCurrency',
            { where: { ...where, createdAt: { [Op.lt]: endDate } } },
            host.currency,
          )
        ).totalInHostCurrency +
        (
          await sumTransactions(
            'platformFeeInHostCurrency',
            { where: { ...where, createdAt: { [Op.lt]: endDate } } },
            host.currency,
          )
        ).totalInHostCurrency,
      delta: await sumTransactions('netAmountInCollectiveCurrency', { where: whereWithDateRange }, host.currency), // delta host balance last month
      backers: await getBackersStats(startDate, endDate, collectiveids),
    };
  };

  const processHost = async host => {
    try {
      summary.totalHosts++;
      console.log('>>> Processing host', host.slug);
      const data = {},
        attachments = [];
      const note = 'using fxrate of the day of the transaction as provided by the ECB. Your effective fxrate may vary.';
      const expensesPerPage = 30; // number of expenses per page of the Table Of Content (for PDF export)

      let collectivesById = {};
      let page = 1;
      let currentPage = 0;

      data.host = host.info;
      data.collective = host.info;
      data.reportDate = endDate;
      data.reportName = reportName;
      data.month = !yearlyReport && moment(startDate).format('MMMM');
      data.year = year;
      data.startDate = startDate;
      data.endDate = endDate;
      data.endDateIncluded = endDateIncluded;
      data.config = pick(config, 'host');
      data.maxSlugSize = 0;
      data.notes = null;
      data.expensesPerPage = [[]];
      data.taxType = host.getTaxType() || 'Taxes';
      data.stats = {};

      const getHostAdminsEmails = host => {
        if (host.type === 'USER') {
          return models.User.findAll({ where: { CollectiveId: host.id } }).map(u => u.email);
        }
        return models.Member.findAll({
          where: {
            CollectiveId: host.id,
            role: { [Op.or]: [MemberRoles.ADMIN, MemberRoles.ACCOUNTANT] },
          },
        }).map(
          admin => {
            return models.User.findOne({
              attributes: ['email'],
              where: { CollectiveId: admin.MemberCollectiveId },
            }).then(user => user.email);
          },
          { concurrency: 1 },
        );
      };

      const processTransaction = async transaction => {
        const t = {
          ...transaction.info,
          Expense: transaction.Expense
            ? {
                ...transaction.Expense.info,
                items: transaction.Expense.items.map(item => item.dataValues),
                PayoutMethod: transaction.Expense.PayoutMethod,
              }
            : null,
        };
        t.collective = collectivesById[t.CollectiveId].dataValues;
        t.collective.shortSlug = t.collective.slug.replace(/^wwcode-?(.)/, '$1');
        t.notes = t.Expense && t.Expense.privateMessage && stripHTML(t.Expense.privateMessage);
        if (t.data && t.data.fxrateSource) {
          t.notes = t.notes ? `${t.notes} (${note})` : note;
          data.notes = note;
        }
        t.source = t.ExpenseId ? 'EXPENSE' : t.OrderId ? 'ORDER' : 'OTHER';

        // We prepare expenses for the PDF export
        if (t.type === 'DEBIT' && t.ExpenseId) {
          t.page = page++;
          if ((page - 1) % expensesPerPage === 0) {
            currentPage++;
            data.expensesPerPage[currentPage] = [];
          }
          data.expensesPerPage[currentPage].push(t);
        }

        data.maxSlugSize = Math.max(data.maxSlugSize, t.collective.shortSlug.length + 1);
        if (!t.description) {
          const source = await transaction.getSource();
          t.description = source.description;
        }
        return t;
      };

      const collectives = await getHostedCollectives(host.id, startDate, endDate);
      collectivesById = keyBy(collectives, 'id');
      data.stats.totalCollectives = collectives.filter(c => c.type === 'COLLECTIVE').length;
      summary.totalCollectives += data.stats.totalCollectives;
      console.log(`>>> processing ${data.stats.totalCollectives} collectives`);
      let transactions = await getTransactions(Object.keys(collectivesById), startDate, endDate, {
        where: { HostCollectiveId: host.id },
        include: [
          {
            model: models.Expense,
            include: [
              'fromCollective',
              {
                model: models.ExpenseItem,
                as: 'items',
                where: {
                  url: { [Op.not]: null },
                },
              },
              { model: models.PayoutMethod, attributes: ['type'] },
            ],
          },
          {
            model: models.User,
            as: 'createdByUser',
          },
          { model: models.PaymentMethod, attributes: ['service', 'type'] },
        ],
      });

      if (!transactions || transactions.length == 0) {
        throw new Error('No transaction found');
      }
      console.log(`>>> processing ${transactions.length} transactions`);
      transactions = await Promise.all(transactions.map(processTransaction));
      const csv = models.Transaction.exportCSV(transactions, collectivesById);
      attachments.push({
        filename: `${host.slug}-${csvFilename}`,
        content: csv,
      });
      data.transactions = transactions;
      // Don't generate PDF in email if it's the yearly report
      let pdf;
      if (!yearlyReport && !process.env.SKIP_PDF) {
        pdf = await exportToPDF('expenses', data, {
          paper: host.currency === 'USD' ? 'Letter' : 'A4',
        }).catch(error => {
          console.error(error);
          return;
        });
      }

      // Mailgun limit is 25MB
      if (pdf && pdf.length < 24000000) {
        attachments.push({
          filename: `${host.slug}-${pdfFilename}`,
          content: pdf,
        });
        data.expensesPdf = true;
      }
      const stats = await getHostStats(host, Object.keys(collectivesById));

      const { donations, expenses, otherCredits, otherDebits } = groupBy(data.transactions, t => {
        if (t.OrderId && t.type === 'CREDIT') {
          return 'donations';
        } else if (t.ExpenseId && t.type === 'DEBIT') {
          return 'expenses';
          // TODO REPLACE WITH OTHER INCOMES AND OTHER EXPENSES
        } else if (t.type === 'DEBIT') {
          return 'otherDebits';
        } else if (t.type === 'CREDIT') {
          return 'otherCredits';
        }
      });

      const totalAmountDonations = sumBy(donations, 'amountInHostCurrency');
      const paymentProcessorFees = sumBy(donations, 'paymentProcessorFeeInHostCurrency');
      const platformFees = sumBy(donations, 'platformFeeInHostCurrency');
      const totalAmountOtherCredits = sumBy(otherCredits, 'amountInHostCurrency');
      const paymentProcessorFeesOtherCredits = sumBy(otherCredits, 'paymentProcessorFeeInHostCurrency');
      const platformFeesOtherCredits = sumBy(otherCredits, 'platformFeeInHostCurrency');
      const totalAmountOtherDebits = sumBy(otherDebits, 'amountInHostCurrency');
      const paymentProcessorFeesOtherDebits = sumBy(otherDebits, 'paymentProcessorFeeInHostCurrency');
      const platformFeesOtherDebits = sumBy(otherDebits, 'platformFeeInHostCurrency');
      const payoutProcessorFeesPaypal = sumByWhen(
        expenses,
        'paymentProcessorFeeInHostCurrency',
        t => t.Expense?.PayoutMethod?.type === 'PAYPAL',
      );
      const payoutProcessorFeesTransferWise = sumByWhen(
        expenses,
        'paymentProcessorFeeInHostCurrency',
        t => t.Expense?.PayoutMethod?.type === 'BANK_ACCOUNT',
      );
      const payoutProcessorFeesOther = sumByWhen(
        expenses,
        'paymentProcessorFeeInHostCurrency',
        t => (t.Expense && !t.Expense.PayoutMethod) || t.Expense?.PayoutMethod?.type === 'OTHER',
      );
      const totalNetAmountReceived =
        totalAmountDonations +
        paymentProcessorFees +
        platformFees +
        totalAmountOtherCredits +
        paymentProcessorFeesOtherCredits +
        platformFeesOtherCredits;
      const totalTaxAmountCollected = sumByWhen(transactions, 'taxAmount', t => t.type === 'CREDIT');
      const totalAmountPaidExpenses = sumByWhen(expenses, 'netAmountInHostCurrency');
      const totalHostFees = sumBy([...donations, ...otherCredits], 'hostFeeInHostCurrency');
      const totalNetAmountReceivedForCollectives = sumBy([...donations, ...otherCredits], 'netAmountInHostCurrency');
      const totalAmountSpent =
        totalAmountPaidExpenses +
        payoutProcessorFeesOther +
        payoutProcessorFeesPaypal +
        totalAmountOtherDebits +
        paymentProcessorFeesOtherDebits +
        platformFeesOtherDebits;

      data.stats = {
        ...data.stats,
        ...stats,
        numberDonations: donations.length,
        numberOtherCredits: otherCredits?.length || 0,
        numberOtherDebits: otherDebits?.length || 0,
        numberPaidExpenses: expenses.length,
        numberTransactions: transactions.length,
        paymentProcessorFees,
        paymentProcessorFeesOtherCredits,
        paymentProcessorFeesOtherDebits,
        payoutProcessorFeesOther,
        payoutProcessorFeesPaypal,
        payoutProcessorFeesTransferWise,
        platformFees,
        platformFeesOtherCredits,
        platformFeesOtherDebits,
        totalActiveCollectives: Object.keys(keyBy(data.transactions, 'CollectiveId')).length,
        totalAmountDonations,
        totalAmountOtherCredits,
        totalAmountOtherDebits,
        totalAmountPaidExpenses,
        totalAmountSpent,
        totalHostFees,
        totalNetAmountReceived,
        totalNetAmountReceivedForCollectives,
        totalTaxAmountCollected,
      };

      summary.hosts.push({
        host: { name: host.name, slug: host.slug, currency: host.currency },
        stats: data.stats,
      });
      summary.totalActiveHosts++;
      summary.totalActiveCollectives += data.stats.totalActiveCollectives;
      summary.numberTransactions += data.stats.numberTransactions;
      summary.numberDonations += data.stats.numberDonations;
      summary.numberPaidExpenses += data.stats.numberPaidExpenses;
      summary.totalAmountPaidExpenses += data.stats.totalAmountPaidExpenses;
      // Don't send transactions in email if there is more than 1000
      if (data.transactions.length > 1000) {
        delete data.transactions;
      }
      const admins = await getHostAdminsEmails(host);
      await sendEmail(admins, data, attachments);
    } catch (e) {
      console.error(`Error in processing host ${host.slug}:`, e);
      debug(e);
    }
  };

  const sendEmail = (recipients, data, attachments) => {
    debug('Sending email to ', recipients);
    if (!recipients || recipients.length === 0) {
      console.error('Unable to send host report for ', data.host.slug, 'No recipient to send to');
      return;
    }
    debug('email data stats', JSON.stringify(data.stats, null, 2));
    const options = { attachments };
    return emailLib.send(emailTemplate, recipients, data, options);
  };

  const query = `
  with "hosts" as (SELECT DISTINCT "HostCollectiveId" AS id FROM "Collectives" WHERE "deletedAt" IS NULL AND "isActive" IS TRUE AND "HostCollectiveId" IS NOT NULL)
  SELECT c.* FROM "Collectives" c WHERE c.id IN (SELECT h.id FROM hosts h) ${previewCondition}
  `;

  const hosts = await sequelize.query(query, {
    model: models.Collective,
    type: sequelize.QueryTypes.SELECT,
  });
  console.log(`Preparing the ${reportName} for ${hosts.length} hosts`);

  return Promise.map(hosts, processHost, { concurrency: 1 }).then(() => {
    console.log('>>> All done. Exiting.');
    process.exit(0);
  });
}

export default HostReport;
<|endoftext|>


<commit_before>'use strict';

var packets = [],
    callbacks = [],
    workingBundle = [];

var maybeEmit = function(latest) {

    if (latest == "\n") {
        var response = workingBundle.join('');
        var numberOfCallbacks = callbacks.length,
            currentIteration = 1;

        for (var y in callbacks) {
            callbacks[y](response);
            if (currentItereation == numberOfCallbacks) {
                for (var x in workingBundle) {
                    packets.push(workingBundle[x]);
                }
                workingBundle = [];
            } else currentIteration++;
        }

    }

}

module.exports.addPacket = function(string) {

    //add strings to the array
    workingBundle.push(string);
    maybeEmit(string);

}

module.exports.onFullResponse = function(next) {

    //add callbacks to the array
    callbacks.push(next);

}<commit_msg>Moved response to common libs<commit_after>'use strict';

var packets = [],
    callbacks = [],
    workingBundle = [];

var maybeEmit = function(latest) {

    if (latest.indexOf(";") == latest.length - 1) {
        var response = workingBundle.join('');
        var numberOfCallbacks = callbacks.length,
            currentIteration = 1;

        if (callbacks.length > 0)
            for (var y in callbacks) {
                callbacks[y](response);
                if (currentIteration == numberOfCallbacks) {
                    for (var x in workingBundle) {
                        packets.push(workingBundle[x]);
                    }
                    workingBundle = [];
                } else currentIteration++;
            }
        else {
            workingBundle = [];
        }

    }

}

module.exports.addPacket = function(string) {

    //add strings to the array
    workingBundle.push(string);
    maybeEmit(string);

}

module.exports.onFullResponse = function(next) {

    //add callbacks to the array
    callbacks.push(next);

}<|endoftext|>


<commit_before>dojo.provide("dojox.embed.Quicktime");

(function(){
	/*******************************************************
		dojox.embed.Quicktime

		Base functionality to insert a QuickTime movie
		into a document on the fly.
	 ******************************************************/

	var qtMarkup, qtVersion, installed, __def__={
		width: 320,
		height: 240,
		redirect: null
	};
	var keyBase="dojox-embed-quicktime-", keyCount=0;

	//	reference to the test movie we will use for getting QT info from the browser.
	var testMovieUrl=dojo.moduleUrl("dojox", "embed/resources/version.mov");

	//	*** private methods *********************************************************
	function prep(kwArgs){
		kwArgs = dojo.mixin(dojo.clone(__def__), kwArgs || {});
		if(!("path" in kwArgs)){
			console.error("dojox.embed.Quicktime(ctor):: no path reference to a QuickTime movie was provided.");
			return null;
		}
		if(!("id" in kwArgs)){
			kwArgs.id=(keyBase + keyCount++);
		}
		return kwArgs;
	}

	var getQTMarkup = 'This content requires the QuickTime plugin.';
	if(dojo.isIE){
		qtVersion = 0;
		installed = (function(){
			try{
				var o = new ActiveXObject("QuickTimeCheckObject.QuickTimeCheck.1");
				if(o!==undefined){
					//	pull the qt version too
					var v=o.QuickTimeVersion.toString(16);
					qtVersion={
						major: parseInt(v.substring(0,1),10)||0,
						minor: parseInt(v.substring(1,2),10)||0,
						rev: parseInt(v.substring(2,3), 10)||0
					};
					return o.IsQuickTimeAvailable(0);
				}
			} catch(e){ }
			return false;
		})();

		qtMarkup = function(kwArgs){
			if(!installed){ return { id: null, markup: getQTMarkup }; }

			kwArgs = prep(kwArgs);
			if(!kwArgs){ return null; }
			var s = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" '
				+ 'codebase="http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0" '
				+ 'id="' + kwArgs.id + '" '
				+ 'width="' + kwArgs.width + '" '
				+ 'height="' + kwArgs.height + '">'
				+ '<param name="src" value="' + kwArgs.path + '" />';
			if(kwArgs.params){
				for(var p in kwArgs.params){
					s += '<param name="' + p + '" value="' + kwArgs.params[p] + '" />';
				}
			}
			s += '</object>';
			return { id: kwArgs.id, markup: s };
		}
	} else {
		installed = (function(){
			for(var i=0, l=navigator.plugins.length; i<l; i++){
				if(navigator.plugins[i].name.indexOf("QuickTime")>-1){
					return true;
				}
			}
			return false;
		})();

		qtMarkup = function(kwArgs){
			if(!installed){ return { id: null, markup: getQTMarkup }; }

			kwArgs = prep(kwArgs);
			if(!kwArgs){ return null; }
			var s = '<embed type="video/quicktime" src="' + kwArgs.path + '" '
				+ 'id="' + kwArgs.id + '" '
				+ 'name="' + kwArgs.id + '" '
				+ 'pluginspage="www.apple.com/quicktime/download" '
				+ 'enablejavascript="true" '
				+ 'width="' + kwArgs.width + '" '
				+ 'height="' + kwArgs.height + '"';
			if(kwArgs.params){
				for(var p in kwArgs.params){
					s += ' ' + p + '="' + kwArgs.params[p] + '"';
				}
			}
			s += '></embed>';
			return { id: kwArgs.id, markup: s };
		}
	}

	/*=====
	dojox.embed.__QTArgs = function(path, id, width, height, params, redirect){
		//	path: String
		//		The URL of the movie to embed.
		//	id: String?
		//		A unique key that will be used as the id of the created markup.  If you don't
		//		provide this, a unique key will be generated.
		//	width: Number?
		//		The width of the embedded movie; the default value is 320px.
		//	height: Number?
		//		The height of the embedded movie; the default value is 240px
		//	params: Object?
		//		A set of key/value pairs that you want to define in the resultant markup.
		//	redirect: String?
		//		A url to redirect the browser to if the current QuickTime version is not supported.
		this.id=id;
		this.path=path;
		this.width=width;
		this.height=height;
		this.params=params;
		this.redirect=redirect;
	}
	=====*/

	dojox.embed.Quicktime=function(/* dojox.embed.__QTArgs */kwArgs, /* DOMNode */node){
		//	summary:
		//		Returns a reference to the HTMLObject/HTMLEmbed that is created to
		//		place the movie in the document.  You can use this either with or
		//		without the new operator.  Note that with any other DOM manipulation,
		//		you must wait until the document is finished loading before trying
		//		to use this.
		//
		//	example:
		//		Embed a QuickTime movie in a document using the new operator, and get a reference to it.
		//	|	var movie = new dojox.embed.QuickTime({
		//	|		path: "path/to/my/movie.mov",
		//	|		width: 400,
		//	|		height: 300
		//	|	}, myWrapperNode);
		//
		//	example:
		//		Embed a movie in a document without using the new operator.
		//	|	var movie = dojox.embed.QuickTime({
		//	|		path: "path/to/my/movie.mov",
		//	|		width: 400,
		//	|		height: 300
		//	|	}, myWrapperNode);

		return dojox.embed.Quicktime.place(kwArgs, node);	//	HTMLObject
	};

	dojo.mixin(dojox.embed.Quicktime, {
		//	summary:
		//		A singleton object used internally to get information
		//		about the QuickTime player available in a browser, and
		//		as the factory for generating and placing markup in a
		//		document.
		//
		//	minSupported: Number
		//		The minimum supported version of the QuickTime Player, defaults to
		//		6.
		//	available: Boolean
		//		Whether or not QuickTime is available.
		//	supported: Boolean
		//		Whether or not the QuickTime Player installed is supported by
		//		dojox.embed.
		//	version: Object
		//		The version of the installed QuickTime Player; takes the form of
		//		{ major, minor, rev }.  To get the major version, you'd do this:
		//		var v=dojox.embed.Quicktime.version.major;
		//	initialized: Boolean
		//		Whether or not the QuickTime engine is available for use.
		//	onInitialize: Function
		//		A stub you can connect to if you are looking to fire code when the
		//		engine becomes available.  A note: do NOT use this stub to embed
		//		a movie in your document; this WILL be fired before DOMContentLoaded
		//		is fired, and you will get an error.  You should use dojo.addOnLoad
		//		to place your movie instead.

		minSupported: 6,
		available: installed,
		supported: installed,
		version: qtVersion,
		initialized: false,
		onInitialize: function(){
			dojox.embed.Quicktime.initialized = true;
		},	//	stub function to let you know when this is ready

		place: function(kwArgs, node){
			var o = qtMarkup(kwArgs);

			node = dojo.byId(node);
			if(!node){
				node=dojo.doc.createElement("div");
				node.id=o.id+"-container";
				dojo.body().appendChild(node);
			}

			if(o){
				node.innerHTML = o.markup;
				if(o.id){
					return (dojo.isIE)? dojo.byId(o.id) : document[o.id];	//	QuickTimeObject
				}
			}
			return null;	//	QuickTimeObject
		}
	});

	//	go get the info
	if(!dojo.isIE){
		// FIXME: Opera does not like this at all for some reason, and of course there's no event references easily found.
		qtVersion = dojox.embed.Quicktime.version = { major: 0, minor: 0, rev: 0 };
		var o = qtMarkup({ path: testMovieUrl, width:4, height:4 });

		function qtInsert(){
			if(!dojo._initFired){
				var s='
'
					+ o.markup
					+ '
';
				document.write(s);
			} else {
				var n = document.createElement("div");
				n.id="-qt-version-test";
				n.style.cssText = "top:0;left:0;width:1px;height:1px;overflow:hidden;position:absolute;";
				dojo.body().appendChild(n);
				n.innerHTML = o.markup;
			}
		}

		function qtGetInfo(mv){
			var qt, n, v = [ 0, 0, 0 ];
			if(mv){
				qt=mv, n=qt.parentNode;
			} else {
				qtInsert();
				if(!dojo.isOpera){
					setTimeout(function(){ qtGetInfo(document[o.id]); }, 50);
				} else {
					var fn=function(){
						setTimeout(function(){ qtGetInfo(document[o.id]) }, 50);
					};
					if(!dojo._initFired){
						dojo.addOnLoad(fn);
					} else {
						dojo.connect(document[o.id], "onload", fn);
					}
				}
				return;
			}

			if(qt){
				try {
					v = qt.GetQuickTimeVersion().split(".");
					qtVersion = { major: parseInt(v[0]||0), minor: parseInt(v[1]||0), rev: parseInt(v[2]||0) };
				} catch(e){
					qtVersion = { major: 0, minor: 0, rev: 0 };
				}
			}

			dojox.embed.Quicktime.supported = v[0];
			dojox.embed.Quicktime.version = qtVersion;
			if(dojox.embed.Quicktime.supported){
				dojox.embed.Quicktime.onInitialize();
			} else {
				console.log("quicktime is not installed.");
			}

			try {
				if(!mv){
					dojo.body().removeChild(n);
				}
			} catch(e){ }
		}

		qtGetInfo();
	}
	else if(dojo.isIE && installed){
		dojox.embed.Quicktime.onInitialize();
	}
})();
<commit_msg>Apply patch from Chris Barber, fixes #7054. !strict.<commit_after>dojo.provide("dojox.embed.Quicktime");

(function(){
	/*******************************************************
		dojox.embed.Quicktime

		Base functionality to insert a QuickTime movie
		into a document on the fly.
	 ******************************************************/

	var qtMarkup, qtVersion, installed, __def__={
		width: 320,
		height: 240,
		redirect: null
	};
	var keyBase="dojox-embed-quicktime-", keyCount=0;

	//	reference to the test movie we will use for getting QT info from the browser.
	var testMovieUrl=dojo.moduleUrl("dojox", "embed/resources/version.mov");

	//	*** private methods *********************************************************
	function prep(kwArgs){
		kwArgs = dojo.mixin(dojo.clone(__def__), kwArgs || {});
		if(!("path" in kwArgs)){
			console.error("dojox.embed.Quicktime(ctor):: no path reference to a QuickTime movie was provided.");
			return null;
		}
		if(!("id" in kwArgs)){
			kwArgs.id=(keyBase + keyCount++);
		}
		return kwArgs;
	}

	var getQTMarkup = 'This content requires the QuickTime plugin.';
	if(dojo.isIE){
		qtVersion = 0;
		installed = (function(){
			try{
				var o = new ActiveXObject("QuickTimeCheckObject.QuickTimeCheck.1");
				if(o!==undefined){
					//	pull the qt version too
					var v=o.QuickTimeVersion.toString(16);
					qtVersion={
						major: parseInt(v.substring(0,1),10)||0,
						minor: parseInt(v.substring(1,2),10)||0,
						rev: parseInt(v.substring(2,3), 10)||0
					};
					return o.IsQuickTimeAvailable(0);
				}
			} catch(e){ }
			return false;
		})();

		qtMarkup = function(kwArgs){
			if(!installed){ return { id: null, markup: getQTMarkup }; }

			kwArgs = prep(kwArgs);
			if(!kwArgs){ return null; }
			var s = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" '
				+ 'codebase="http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0" '
				+ 'id="' + kwArgs.id + '" '
				+ 'width="' + kwArgs.width + '" '
				+ 'height="' + kwArgs.height + '">'
				+ '<param name="src" value="' + kwArgs.path + '" />';
			if(kwArgs.params){
				for(var p in kwArgs.params){
					s += '<param name="' + p + '" value="' + kwArgs.params[p] + '" />';
				}
			}
			s += '</object>';
			return { id: kwArgs.id, markup: s };
		}
	} else {
		installed = (function(){
			for(var i=0, l=navigator.plugins.length; i<l; i++){
				if(navigator.plugins[i].name.indexOf("QuickTime")>-1){
					return true;
				}
			}
			return false;
		})();

		qtMarkup = function(kwArgs){
			if(!installed){ return { id: null, markup: getQTMarkup }; }

			kwArgs = prep(kwArgs);
			if(!kwArgs){ return null; }
			var s = '<embed type="video/quicktime" src="' + kwArgs.path + '" '
				+ 'id="' + kwArgs.id + '" '
				+ 'name="' + kwArgs.id + '" '
				+ 'pluginspage="www.apple.com/quicktime/download" '
				+ 'enablejavascript="true" '
				+ 'width="' + kwArgs.width + '" '
				+ 'height="' + kwArgs.height + '"';
			if(kwArgs.params){
				for(var p in kwArgs.params){
					s += ' ' + p + '="' + kwArgs.params[p] + '"';
				}
			}
			s += '></embed>';
			return { id: kwArgs.id, markup: s };
		}
	}

	/*=====
	dojox.embed.__QTArgs = function(path, id, width, height, params, redirect){
		//	path: String
		//		The URL of the movie to embed.
		//	id: String?
		//		A unique key that will be used as the id of the created markup.  If you don't
		//		provide this, a unique key will be generated.
		//	width: Number?
		//		The width of the embedded movie; the default value is 320px.
		//	height: Number?
		//		The height of the embedded movie; the default value is 240px
		//	params: Object?
		//		A set of key/value pairs that you want to define in the resultant markup.
		//	redirect: String?
		//		A url to redirect the browser to if the current QuickTime version is not supported.
		this.id=id;
		this.path=path;
		this.width=width;
		this.height=height;
		this.params=params;
		this.redirect=redirect;
	}
	=====*/

	dojox.embed.Quicktime=function(/* dojox.embed.__QTArgs */kwArgs, /* DOMNode */node){
		//	summary:
		//		Returns a reference to the HTMLObject/HTMLEmbed that is created to
		//		place the movie in the document.  You can use this either with or
		//		without the new operator.  Note that with any other DOM manipulation,
		//		you must wait until the document is finished loading before trying
		//		to use this.
		//
		//	example:
		//		Embed a QuickTime movie in a document using the new operator, and get a reference to it.
		//	|	var movie = new dojox.embed.QuickTime({
		//	|		path: "path/to/my/movie.mov",
		//	|		width: 400,
		//	|		height: 300
		//	|	}, myWrapperNode);
		//
		//	example:
		//		Embed a movie in a document without using the new operator.
		//	|	var movie = dojox.embed.QuickTime({
		//	|		path: "path/to/my/movie.mov",
		//	|		width: 400,
		//	|		height: 300
		//	|	}, myWrapperNode);

		return dojox.embed.Quicktime.place(kwArgs, node);	//	HTMLObject
	};

	dojo.mixin(dojox.embed.Quicktime, {
		//	summary:
		//		A singleton object used internally to get information
		//		about the QuickTime player available in a browser, and
		//		as the factory for generating and placing markup in a
		//		document.
		//
		//	minSupported: Number
		//		The minimum supported version of the QuickTime Player, defaults to
		//		6.
		//	available: Boolean
		//		Whether or not QuickTime is available.
		//	supported: Boolean
		//		Whether or not the QuickTime Player installed is supported by
		//		dojox.embed.
		//	version: Object
		//		The version of the installed QuickTime Player; takes the form of
		//		{ major, minor, rev }.  To get the major version, you'd do this:
		//		var v=dojox.embed.Quicktime.version.major;
		//	initialized: Boolean
		//		Whether or not the QuickTime engine is available for use.
		//	onInitialize: Function
		//		A stub you can connect to if you are looking to fire code when the
		//		engine becomes available.  A note: do NOT use this stub to embed
		//		a movie in your document; this WILL be fired before DOMContentLoaded
		//		is fired, and you will get an error.  You should use dojo.addOnLoad
		//		to place your movie instead.

		minSupported: 6,
		available: installed,
		supported: installed,
		version: qtVersion,
		initialized: false,
		onInitialize: function(){
			dojox.embed.Quicktime.initialized = true;
		},	//	stub function to let you know when this is ready

		place: function(kwArgs, node){
			var o = qtMarkup(kwArgs);

			node = dojo.byId(node);
			if(!node){
				node=dojo.doc.createElement("div");
				node.id=o.id+"-container";
				dojo.body().appendChild(node);
			}

			if(o){
				node.innerHTML = o.markup;
				if(o.id){
					return (dojo.isIE)? dojo.byId(o.id) : document[o.id];	//	QuickTimeObject
				}
			}
			return null;	//	QuickTimeObject
		}
	});

	//	go get the info
	if(!dojo.isIE){
		// FIXME: Opera does not like this at all for some reason, and of course there's no event references easily found.
		qtVersion = dojox.embed.Quicktime.version = { major: 0, minor: 0, rev: 0 };
		var o = qtMarkup({ path: testMovieUrl, width:4, height:4 });

		function qtInsert(){
			if(!dojo._initFired){
				var s='
'
					+ o.markup
					+ '
';
				document.write(s);
			} else {
				var n = document.createElement("div");
				n.id="-qt-version-test";
				n.style.cssText = "top:0;left:0;width:1px;height:1px;overflow:hidden;position:absolute;";
				dojo.body().appendChild(n);
				n.innerHTML = o.markup;
			}
		}

		function qtGetInfo(mv){
			var qt, n, v = [ 0, 0, 0 ];
			if(mv){
				qt=mv, n=qt.parentNode;
			} else {
				if(o.id) {
					qtInsert();
					if(!dojo.isOpera){
						setTimeout(function(){ qtGetInfo(document[o.id]); }, 50);
					} else {
						var fn=function(){
							setTimeout(function(){ qtGetInfo(document[o.id]) }, 50);
						};
						if(!dojo._initFired){
							dojo.addOnLoad(fn);
						} else {
							dojo.connect(document[o.id], "onload", fn);
						}
					}
				}
				return;
			}

			if(qt){
				try {
					v = qt.GetQuickTimeVersion().split(".");
					qtVersion = { major: parseInt(v[0]||0), minor: parseInt(v[1]||0), rev: parseInt(v[2]||0) };
				} catch(e){
					qtVersion = { major: 0, minor: 0, rev: 0 };
				}
			}

			dojox.embed.Quicktime.supported = v[0];
			dojox.embed.Quicktime.version = qtVersion;
			if(dojox.embed.Quicktime.supported){
				dojox.embed.Quicktime.onInitialize();
			} else {
				console.log("quicktime is not installed.");
			}

			try {
				if(!mv){
					dojo.body().removeChild(n);
				}
			} catch(e){ }
		}

		qtGetInfo();
	}
	else if(dojo.isIE && installed){
		dojox.embed.Quicktime.onInitialize();
	}
})();
<|endoftext|>