<commit_before>var cfg         = require('nconf'),
    fs          = require('fs'),
    _           = require('underscore'),
    _s          = require('underscore.string'),
    when        = require('when'),
    node        = require('when/node'),
    readdir     = node.lift(fs.readdir);
    join        = require('path').join,
    themesDir   = join(cfg.get('paths:clientStatic'), 'css/theme');

module.exports.index = function (req, res) {
    readdir(themesDir)
        .then(namesToThemes)
        .tap(selectActive)
        .then(res.json.bind(res))
        .catch(res.send.bind(res, 500));

    function selectActive (themes) {
        var active = _.findWhere(themes, { id:cfg.get('admin:theme') });
        if (active) active.active = true;
    }

    function nameToTheme (name) {
        return {
            id: name,
            name: _s.titleize(_s.humanize(name))
        }
    }

    function namesToThemes (fileNames) {
        return fileNames.map(nameToTheme);
    }
};

module.exports.update = function (req, res) {
    readdir(themesDir)
        .then(checkThemes)
        .then(setAdminTheme)
        .done(
            res.json.bind(res),
            res.send.bind(res, 400)
        );

    function checkThemes (themes) {
        return (themes.indexOf(req.params.admin) < 0) ? when.reject() : req.params.admin;
    }

    function setAdminTheme (theme) {
        cfg.set('admin:theme', req.params.admin);
    }

    // fs.readdir(themesDir, function (err, dirs) {
    //     if (err) return res.send(500);
    //     if (dirs.indexOf(req.params.admintheme) < 0) return res.send(400);

    //     cfg.set('admin:theme', req.params.admintheme);
    //     res.json({success:true});
    // });
};<commit_msg>API updates<commit_after>var cfg         = require('nconf'),
    fs          = require('fs'),
    _           = require('underscore'),
    _s          = require('underscore.string'),
    when        = require('when'),
    node        = require('when/node'),
    readdir     = node.lift(fs.readdir);
    join        = require('path').join,
    themesDir   = join(cfg.get('paths:clientStatic'), 'css/theme');

module.exports.index = function (req, res) {
    readdir(themesDir)
        .then(namesToThemes)
        .tap(selectActive)
        .done(
            res.json.bind(res),
            res.send.bind(res, 500)
        );

    function selectActive (themes) {
        var active = _.findWhere(themes, { id:cfg.get('admin:theme') });
        if (active) active.active = true;
    }

    function nameToTheme (name) {
        return {
            id: name,
            name: _s.titleize(_s.humanize(name))
        }
    }

    function namesToThemes (fileNames) {
        return fileNames.map(nameToTheme);
    }
};

module.exports.update = function (req, res) {
    readdir(themesDir)
        .then(checkThemes)
        .then(setAdminTheme)
        .done(
            res.json.bind(res),
            res.send.bind(res, 400)
        );

    function checkThemes (themes) {
        return (themes.indexOf(req.params.admin) < 0) ? when.reject() : req.params.admin;
    }

    function setAdminTheme (theme) {
        cfg.set('admin:theme', req.params.admin);
    }
};<|endoftext|>


<commit_before>var _ = require('lodash'),
    path = require('path'),
    api = require('../../api'),
    BaseMapGenerator = require('./base-generator'),
    config = require('../../config');

// A class responsible for generating a sitemap from posts and keeping it updated
function UserMapGenerator(opts) {
    _.extend(this, _.defaults(opts || {}, UserMapGenerator.Defaults));

    BaseMapGenerator.apply(this, arguments);
}

UserMapGenerator.Defaults = {
    // TODO?
};

// Inherit from the base generator class
_.extend(UserMapGenerator.prototype, BaseMapGenerator.prototype);

_.extend(UserMapGenerator.prototype, {
    getData: function () {
        return api.users.browse({
            context: {
                internal: true
            },
            limit: 'all'
        }).then(function (resp) {
            return resp.users;
        });
    },

    getUrlForDatum: function (user, permalinks) {
        return config.urlFor('author', {author: user, permalinks: permalinks}, true);
    },

    getPriorityForDatum: function () {
        // TODO: We could influence this with meta information
        return 0.6;
    },

    createUrlNodeFromDatum: function (datum) {
        var orig = BaseMapGenerator.prototype.createUrlNodeFromDatum.apply(this, arguments),
            imageUrl,
            imageEl;

        // Check for image and add it
        if (datum.image) {
            // Grab the image url
            imageUrl = this.getUrlForImage(datum.image);
            // Create the weird xml node syntax structure that is expected
            imageEl = [
                {'image:loc': imageUrl},
                {'image:caption': path.basename(imageUrl)}
            ];
            // Add the node to the url xml node
            orig.url.push({
                'image:image': imageEl
            });
        }

        return orig;
    }
});

module.exports = UserMapGenerator;
<commit_msg>Fixes sitemap image errors<commit_after>var _                   = require('lodash'),
    path                = require('path'),
    api                 = require('../../api'),
    BaseMapGenerator    = require('./base-generator'),
    validator           = require('validator'),
    config              = require('../../config');

// A class responsible for generating a sitemap from posts and keeping it updated
function UserMapGenerator(opts) {
    _.extend(this, _.defaults(opts || {}, UserMapGenerator.Defaults));

    BaseMapGenerator.apply(this, arguments);
}

UserMapGenerator.Defaults = {
    // TODO?
};

// Inherit from the base generator class
_.extend(UserMapGenerator.prototype, BaseMapGenerator.prototype);

_.extend(UserMapGenerator.prototype, {
    getData: function () {
        return api.users.browse({
            context: {
                internal: true
            },
            limit: 'all'
        }).then(function (resp) {
            return resp.users;
        });
    },

    getUrlForDatum: function (user, permalinks) {
        return config.urlFor('author', {author: user, permalinks: permalinks}, true);
    },

    getPriorityForDatum: function () {
        // TODO: We could influence this with meta information
        return 0.6;
    },

    createUrlNodeFromDatum: function (datum) {
        var orig = BaseMapGenerator.prototype.createUrlNodeFromDatum.apply(this, arguments),
            imageUrl,
            imageEl;

        // Check for image and add it
        if (datum.cover) {
            // Grab the image url
            imageUrl = this.getUrlForImage(datum.cover);
            imageUrl = imageUrl.substring(0, 2) === '//' ? 'http:' + imageUrl : imageUrl;
            if (validator.isURL(imageUrl, {protocols: ['http', 'https'], require_protocol: true})) {
                // Create the weird xml node syntax structure that is expected
                imageEl = [
                    {'image:loc': imageUrl},
                    {'image:caption': path.basename(imageUrl)}
                ];
                // Add the node to the url xml node
                orig.url.push({
                    'image:image': imageEl
                });
            }
        }

        return orig;
    }
});

module.exports = UserMapGenerator;
<|endoftext|>


<commit_before>global.window = {};

var should = require('should');
var React = require('react');
var router = require('../lib/router');

function resetAndroidApi() {
  router.android = {
    getUserSession: () => {},
    getRouteParams: () => {},
    getRouteData: () => {},
    getMemStorage: () => {},
    setMemStorage: () => {}
  };
}

describe('router', function () {

  afterEach(resetAndroidApi);

  describe('#getRouteParams', function () {
    beforeEach(resetAndroidApi);

    it('should return test data when window.Android does not exist', function () {
      router.android = null;
      should.deepEqual(router.getRouteParams(), {
        user: 1,
        project: 1,
        page: 1,
        element: 1,
        mode: 'play',
        propertyName: 'borderColor'
      });
    });

    it('should return undefined route params as an empty object', function () {
      router.android.getRouteParams = () => {};
      should.deepEqual(router.getRouteParams(), {});
    });

    it('should return an empty string as an empty object', function () {
      router.android.getRouteParams = () => '';
      should.deepEqual(router.getRouteParams(), {});
    });

    it('should return an invalid string as an empty object', function () {
      router.android.getRouteParams = () => 'invalid blah';
      should.deepEqual(router.getRouteParams(), {});
    });

    it('should return route params as json', function () {
      var fakeRoute = {project: 1, page: 2};
      router.android.getRouteParams = () => {
        return JSON.stringify(fakeRoute);
      };

      should.deepEqual(router.getRouteParams(), fakeRoute);
    });

  });

  describe('#getRouteData', function () {
    beforeEach(resetAndroidApi);

    it('should get return an empty object if no window.Android', function () {
      router.android = null;
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return an empty object if routeData is undefined', function () {
      router.android.getRouteData = () => {};
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return an empty object if routeData is an empty string', function () {
      router.android.getRouteData = () => '';
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return an empty object if routeData is invalid JSON', function () {
      router.android.getRouteData = () => 'invalid';
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return routeData as an object', function () {
      var fakeData = {foo: 'bar', baz: 'qux'};
      router.android.getRouteData = () => {
        return JSON.stringify(fakeData);
      };
      should.deepEqual(router.getRouteData(), fakeData);
    });

  });

  describe('#getUserSession', function () {
    beforeEach(resetAndroidApi);

    it('should return test data if no window.Android', function () {
      router.android = null;
      should.deepEqual(router.getUserSession(),  {
        user: {
          id: 1,
          username: 'jonatmozilla',
          avatar: 'https://secure.gravatar.com/avatar/c1b6f037ee7e440940d3c0c6ebeb8ba2?d=https%3A%2F%2Fstuff.webmaker.org%2Favatars%2Fwebmaker-avatar-200x200.png',
          prefLocale: 'en-US'
        },
        token: 'validToken'
      });
    });

    it('should return user and token if they exist', function () {
      var fakeSession = {
        user: {
          username: 'blah',
          id: 123
        },
        token: 'fooT0ken'
      };
      router.android.getUserSession = () => JSON.stringify(fakeSession);
      should.deepEqual(router.getUserSession(), fakeSession);
    });

    it('should return an empty object if there is no session', function () {
      router.android.getUserSession = () => {};
      should.deepEqual(router.getUserSession(), {});
    });

    it('should return an empty object if the session is invalid JSON', function () {
      router.android.getUserSession = () => '';
      should.deepEqual(router.getUserSession(), {});
    });
  });

  describe('#getInitialState', function () {
    beforeEach(resetAndroidApi);
    it('should add appropriate empty objects for params, routeData, token, and user', function () {
      should.deepEqual(router.getInitialState(), {
        params: {},
        routeData: {},
        user: {},
        token: ''
      });
    });
    it('should return appropriate test data if no window.Android', function () {
      router.android = null;
      should.deepEqual(router.getInitialState(), {
        params: router.getRouteParams(),
        routeData: router.getRouteData(),
        user: router.getUserSession().user,
        token: router.getUserSession().token
      });
    });
  });

});
<commit_msg>Add clearRouteData mock function to tests<commit_after>global.window = {};

var should = require('should');
var React = require('react');
var router = require('../lib/router');

function resetAndroidApi() {
  router.android = {
    getUserSession: () => {},
    getRouteParams: () => {},
    getRouteData: () => {},
    clearRouteData: () => {},
    getMemStorage: () => {},
    setMemStorage: () => {}
  };
}

describe('router', function () {

  afterEach(resetAndroidApi);

  describe('#getRouteParams', function () {
    beforeEach(resetAndroidApi);

    it('should return test data when window.Android does not exist', function () {
      router.android = null;
      should.deepEqual(router.getRouteParams(), {
        user: 1,
        project: 1,
        page: 1,
        element: 1,
        mode: 'play',
        propertyName: 'borderColor'
      });
    });

    it('should return undefined route params as an empty object', function () {
      router.android.getRouteParams = () => {};
      should.deepEqual(router.getRouteParams(), {});
    });

    it('should return an empty string as an empty object', function () {
      router.android.getRouteParams = () => '';
      should.deepEqual(router.getRouteParams(), {});
    });

    it('should return an invalid string as an empty object', function () {
      router.android.getRouteParams = () => 'invalid blah';
      should.deepEqual(router.getRouteParams(), {});
    });

    it('should return route params as json', function () {
      var fakeRoute = {project: 1, page: 2};
      router.android.getRouteParams = () => {
        return JSON.stringify(fakeRoute);
      };

      should.deepEqual(router.getRouteParams(), fakeRoute);
    });

  });

  describe('#getRouteData', function () {
    beforeEach(resetAndroidApi);

    it('should get return an empty object if no window.Android', function () {
      router.android = null;
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return an empty object if routeData is undefined', function () {
      router.android.getRouteData = () => {};
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return an empty object if routeData is an empty string', function () {
      router.android.getRouteData = () => '';
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return an empty object if routeData is invalid JSON', function () {
      router.android.getRouteData = () => 'invalid';
      should.deepEqual(router.getRouteData(), {});
    });

    it('should return routeData as an object', function () {
      var fakeData = {foo: 'bar', baz: 'qux'};
      router.android.getRouteData = () => {
        return JSON.stringify(fakeData);
      };
      should.deepEqual(router.getRouteData(), fakeData);
    });

  });

  describe('#getUserSession', function () {
    beforeEach(resetAndroidApi);

    it('should return test data if no window.Android', function () {
      router.android = null;
      should.deepEqual(router.getUserSession(),  {
        user: {
          id: 1,
          username: 'jonatmozilla',
          avatar: 'https://secure.gravatar.com/avatar/c1b6f037ee7e440940d3c0c6ebeb8ba2?d=https%3A%2F%2Fstuff.webmaker.org%2Favatars%2Fwebmaker-avatar-200x200.png',
          prefLocale: 'en-US'
        },
        token: 'validToken'
      });
    });

    it('should return user and token if they exist', function () {
      var fakeSession = {
        user: {
          username: 'blah',
          id: 123
        },
        token: 'fooT0ken'
      };
      router.android.getUserSession = () => JSON.stringify(fakeSession);
      should.deepEqual(router.getUserSession(), fakeSession);
    });

    it('should return an empty object if there is no session', function () {
      router.android.getUserSession = () => {};
      should.deepEqual(router.getUserSession(), {});
    });

    it('should return an empty object if the session is invalid JSON', function () {
      router.android.getUserSession = () => '';
      should.deepEqual(router.getUserSession(), {});
    });
  });

  describe('#getInitialState', function () {
    beforeEach(resetAndroidApi);
    it('should add appropriate empty objects for params, routeData, token, and user', function () {
      should.deepEqual(router.getInitialState(), {
        params: {},
        routeData: {},
        user: {},
        token: ''
      });
    });
    it('should return appropriate test data if no window.Android', function () {
      router.android = null;
      should.deepEqual(router.getInitialState(), {
        params: router.getRouteParams(),
        routeData: router.getRouteData(),
        user: router.getUserSession().user,
        token: router.getUserSession().token
      });
    });
  });

});
<|endoftext|>


<commit_before>// Compare by given property

'use strict';

var f       = require('../Function/functionalize')
  , compare = require('./compare').call;

var cache = {};

module.exports = function (name) {
	return cache[name] || (cache[name] = f(function (other) {
		return compare(this[name], other[name]);
	}));
};
<commit_msg>'compareBy' now uses 'cache' function<commit_after>// Compare by given property

'use strict';

var f       = require('../Function/functionalize')
  , cache   = require('../Function/cache')
  , compare = require('./compare').call;

module.exports = cache(function (name) {
	return f(function (other) {
		return compare(this[name], other[name]);
	});
}, [String]);
<|endoftext|>


<commit_before>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Events.js
 * @requires OpenLayers/Icon.js
 */

/**
 * Class: OpenLayers.Marker
 * Instances of OpenLayers.Marker are a combination of a
 * <OpenLayers.LonLat> and an <OpenLayers.Icon>.
 *
 * Markers are generally added to a special layer called
 * <OpenLayers.Layer.Markers>.
 *
 * Example:
 * (code)
 * var markers = new OpenLayers.Layer.Markers( "Markers" );
 * map.addLayer(markers);
 *
 * var size = new OpenLayers.Size(10,17);
 * var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
 * var icon = new OpenLayers.Icon('http://boston.openguides.org/markers/AQUA.png',size,offset);
 * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon));
 * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon.clone()));
 *
 * (end)
 *
 * Note that if you pass an icon into the Marker constructor, it will take
 * that icon and use it. This means that you should not share icons between
 * markers -- you use them once, but you should clone() for any additional
 * markers using that same icon.
 */
OpenLayers.Marker = OpenLayers.Class({

    /**
     * Property: icon
     * {<OpenLayers.Icon>} The icon used by this marker.
     */
    icon: null,

    /**
     * Property: lonlat
     * {<OpenLayers.LonLat>} location of object
     */
    lonlat: null,

    /**
     * Property: events
     * {<OpenLayers.Events>} the event handler.
     */
    events: null,

    /**
     * Property: map
     * {<OpenLayers.Map>} the map this marker is attached to
     */
    map: null,

    /**
     * Constructor: OpenLayers.Marker
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>} the position of this marker
     * icon - {<OpenLayers.Icon>}  the icon for this marker
     */
    initialize: function(lonlat, icon) {
        this.lonlat = lonlat;

        var newIcon = (icon) ? icon : OpenLayers.Marker.defaultIcon();
        if (this.icon == null) {
            this.icon = newIcon;
        } else {
            this.icon.url = newIcon.url;
            this.icon.size = newIcon.size;
            this.icon.offset = newIcon.offset;
            this.icon.calculateOffset = newIcon.calculateOffset;
        }
    },

    /**
     * APIMethod: destroy
     * Destroy the marker. You must first remove the marker from any
     * layer which it has been added to, or you will get buggy behavior.
     * (This can not be done within the marker since the marker does not
     * know which layer it is attached to.)
     */
    destroy: function() {
        // erase any drawn features
        this.erase();

        this.map = null;

        if (this.icon != null) {
            this.icon.destroy();
            this.icon = null;
        }
    },

    /**
    * Method: draw
    * Calls draw on the icon, and returns that output.
    *
    * Parameters:
    * px - {<OpenLayers.Pixel>}
    *
    * Returns:
    * {DOMElement} A new DOM Image with this marker's icon set at the
    * location passed-in
    */
    draw: function(px) {
        if (!this.events) {
            this.events = new OpenLayers.Events(this, this.icon.imageDiv, null);
        }
        return this.icon.draw(px);
    },

    /**
    * Method: erase
    * Erases any drawn elements for this marker.
    */
    erase: function() {
        if (this.icon != null) {
            this.icon.erase();
        }
        if (this.events) {
            this.events.destroy();
            this.events = null;
        }
    },

    /**
    * Method: moveTo
    * Move the marker to the new location.
    *
    * Parameters:
    * px - {<OpenLayers.Pixel>} the pixel position to move to
    */
    moveTo: function (px) {
        if ((px != null) && (this.icon != null)) {
            this.icon.moveTo(px);
        }
        this.lonlat = this.map.getLonLatFromLayerPx(px);
    },

    /**
     * APIMethod: isDrawn
     *
     * Returns:
     * {Boolean} Whether or not the marker is drawn.
     */
    isDrawn: function() {
        var isDrawn = (this.icon && this.icon.isDrawn());
        return isDrawn;
    },

    /**
     * Method: onScreen
     *
     * Returns:
     * {Boolean} Whether or not the marker is currently visible on screen.
     */
    onScreen:function() {

        var onScreen = false;
        if (this.map) {
            var screenBounds = this.map.getExtent();
            onScreen = screenBounds.containsLonLat(this.lonlat);
        }
        return onScreen;
    },

    /**
     * Method: inflate
     * Englarges the markers icon by the specified ratio.
     *
     * Parameters:
     * inflate - {float} the ratio to enlarge the marker by (passing 2
     *                   will double the size).
     */
    inflate: function(inflate) {
        if (this.icon) {
            var newSize = new OpenLayers.Size(this.icon.size.w * inflate,
                                              this.icon.size.h * inflate);
            this.icon.setSize(newSize);
        }
    },

    /**
     * Method: setOpacity
     * Change the opacity of the marker by changin the opacity of
     *   its icon
     *
     * Parameters:
     * opacity - {float}  Specified as fraction (0.4, etc)
     */
    setOpacity: function(opacity) {
        this.icon.setOpacity(opacity);
    },

    /**
     * Method: setUrl
     * Change URL of the Icon Image.
     *
     * url - {String}
     */
    setUrl: function(url) {
        this.icon.setUrl(url);
    },

    /**
     * Method: display
     * Hide or show the icon
     *
     * display - {Boolean}
     */
    display: function(display) {
        this.icon.display(display);
    },

    CLASS_NAME: "OpenLayers.Marker"
});

/**
 * Function: defaultIcon
 * Creates a default <OpenLayers.Icon>.
 *
 * Returns:
 * {<OpenLayers.Icon>} A default OpenLayers.Icon to use for a marker
 */
OpenLayers.Marker.defaultIcon = function() {
    var url = OpenLayers.Util.getImagesLocation() + "marker.png";
    var size = new OpenLayers.Size(21, 25);
    var calculateOffset = function(size) {
                    return new OpenLayers.Pixel(-(size.w/2), -size.h);
                 };

    return new OpenLayers.Icon(url, size, null, calculateOffset);
};

<commit_msg>reverted change from r9754 because it breaks applications that register an event before the marker is drawn (see #2258)<commit_after>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Events.js
 * @requires OpenLayers/Icon.js
 */

/**
 * Class: OpenLayers.Marker
 * Instances of OpenLayers.Marker are a combination of a
 * <OpenLayers.LonLat> and an <OpenLayers.Icon>.
 *
 * Markers are generally added to a special layer called
 * <OpenLayers.Layer.Markers>.
 *
 * Example:
 * (code)
 * var markers = new OpenLayers.Layer.Markers( "Markers" );
 * map.addLayer(markers);
 *
 * var size = new OpenLayers.Size(10,17);
 * var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
 * var icon = new OpenLayers.Icon('http://boston.openguides.org/markers/AQUA.png',size,offset);
 * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon));
 * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon.clone()));
 *
 * (end)
 *
 * Note that if you pass an icon into the Marker constructor, it will take
 * that icon and use it. This means that you should not share icons between
 * markers -- you use them once, but you should clone() for any additional
 * markers using that same icon.
 */
OpenLayers.Marker = OpenLayers.Class({

    /**
     * Property: icon
     * {<OpenLayers.Icon>} The icon used by this marker.
     */
    icon: null,

    /**
     * Property: lonlat
     * {<OpenLayers.LonLat>} location of object
     */
    lonlat: null,

    /**
     * Property: events
     * {<OpenLayers.Events>} the event handler.
     */
    events: null,

    /**
     * Property: map
     * {<OpenLayers.Map>} the map this marker is attached to
     */
    map: null,

    /**
     * Constructor: OpenLayers.Marker
     * Parameters:
     * lonlat - {<OpenLayers.LonLat>} the position of this marker
     * icon - {<OpenLayers.Icon>}  the icon for this marker
     */
    initialize: function(lonlat, icon) {
        this.lonlat = lonlat;

        var newIcon = (icon) ? icon : OpenLayers.Marker.defaultIcon();
        if (this.icon == null) {
            this.icon = newIcon;
        } else {
            this.icon.url = newIcon.url;
            this.icon.size = newIcon.size;
            this.icon.offset = newIcon.offset;
            this.icon.calculateOffset = newIcon.calculateOffset;
        }
        this.events = new OpenLayers.Events(this, this.icon.imageDiv, null);
    },

    /**
     * APIMethod: destroy
     * Destroy the marker. You must first remove the marker from any
     * layer which it has been added to, or you will get buggy behavior.
     * (This can not be done within the marker since the marker does not
     * know which layer it is attached to.)
     */
    destroy: function() {
        // erase any drawn features
        this.erase();

        this.map = null;

        this.events.destroy();
        this.events = null;

        if (this.icon != null) {
            this.icon.destroy();
            this.icon = null;
        }
    },

    /**
    * Method: draw
    * Calls draw on the icon, and returns that output.
    *
    * Parameters:
    * px - {<OpenLayers.Pixel>}
    *
    * Returns:
    * {DOMElement} A new DOM Image with this marker's icon set at the
    * location passed-in
    */
    draw: function(px) {
        return this.icon.draw(px);
    },

    /**
    * Method: erase
    * Erases any drawn elements for this marker.
    */
    erase: function() {
        if (this.icon != null) {
            this.icon.erase();
        }
    },

    /**
    * Method: moveTo
    * Move the marker to the new location.
    *
    * Parameters:
    * px - {<OpenLayers.Pixel>} the pixel position to move to
    */
    moveTo: function (px) {
        if ((px != null) && (this.icon != null)) {
            this.icon.moveTo(px);
        }
        this.lonlat = this.map.getLonLatFromLayerPx(px);
    },

    /**
     * APIMethod: isDrawn
     *
     * Returns:
     * {Boolean} Whether or not the marker is drawn.
     */
    isDrawn: function() {
        var isDrawn = (this.icon && this.icon.isDrawn());
        return isDrawn;
    },

    /**
     * Method: onScreen
     *
     * Returns:
     * {Boolean} Whether or not the marker is currently visible on screen.
     */
    onScreen:function() {

        var onScreen = false;
        if (this.map) {
            var screenBounds = this.map.getExtent();
            onScreen = screenBounds.containsLonLat(this.lonlat);
        }
        return onScreen;
    },

    /**
     * Method: inflate
     * Englarges the markers icon by the specified ratio.
     *
     * Parameters:
     * inflate - {float} the ratio to enlarge the marker by (passing 2
     *                   will double the size).
     */
    inflate: function(inflate) {
        if (this.icon) {
            var newSize = new OpenLayers.Size(this.icon.size.w * inflate,
                                              this.icon.size.h * inflate);
            this.icon.setSize(newSize);
        }
    },

    /**
     * Method: setOpacity
     * Change the opacity of the marker by changin the opacity of
     *   its icon
     *
     * Parameters:
     * opacity - {float}  Specified as fraction (0.4, etc)
     */
    setOpacity: function(opacity) {
        this.icon.setOpacity(opacity);
    },

    /**
     * Method: setUrl
     * Change URL of the Icon Image.
     *
     * url - {String}
     */
    setUrl: function(url) {
        this.icon.setUrl(url);
    },

    /**
     * Method: display
     * Hide or show the icon
     *
     * display - {Boolean}
     */
    display: function(display) {
        this.icon.display(display);
    },

    CLASS_NAME: "OpenLayers.Marker"
});

/**
 * Function: defaultIcon
 * Creates a default <OpenLayers.Icon>.
 *
 * Returns:
 * {<OpenLayers.Icon>} A default OpenLayers.Icon to use for a marker
 */
OpenLayers.Marker.defaultIcon = function() {
    var url = OpenLayers.Util.getImagesLocation() + "marker.png";
    var size = new OpenLayers.Size(21, 25);
    var calculateOffset = function(size) {
                    return new OpenLayers.Pixel(-(size.w/2), -size.h);
                 };

    return new OpenLayers.Icon(url, size, null, calculateOffset);
};

<|endoftext|>


<commit_before>// Codi del la funció "Solucions La Galera Text (Autocorrectors)"
// Autor: Marc Clascà
// (c)2013
// Visiteu pàgina oficial per instalació i més informació mclasca.sytes.net/solucions/

(function () {
    console.log("Script iniciat");

    url = document.URL;

    console.log(url);
    url_array = url.split("/");

    if(url_array[2] != "projecteeso.lagaleratext.cat"){
        console.log("Not in lagalera text, exiting...");
        alert("No estàs a la galera text!!");
        return;
    }

    act_code = url_array[4];
    act_code_array = act_code.split("-");
    llibre = act_code_array[0] + "-" + act_code_array[1] + "-" + act_code_array[2];

    console.log(act_code+"\n"+llibre);

    console.log("data retrieved, builing URL...");

    respostes_pdf_url = "http://projecteeso.lagaleratext.cat/static/llibres/" + llibre + "/MEDIES/" + act_code + "-R.pdf";
    console.log(respostes_pdf_url);

    title = "Donde está el coche, donde está el coche!";

    if(!(window.open(respostes_pdf_url, title))){
    	console.log("No s'ha pogut obrir la finestra.")
    };
})()
<commit_msg>Window popup update<commit_after>// Codi del la funció "Solucions La Galera Text (Autocorrectors)"
// Autor: Marc Clascà
// (c)2013
// Visiteu pàgina oficial per instalació i més informació mclasca.sytes.net/solucions/

(function () {
    console.log("Script iniciat");

    url = document.URL;

    console.log(url);
    url_array = url.split("/");

    if(url_array[2] != "projecteeso.lagaleratext.cat"){
        console.log("Not in lagalera text, exiting...");
        alert("No estas a la galera text!!");
        return;
    }else if(url_array)

    act_code = url_array[4];
    act_code_array = act_code.split("-");
    llibre = act_code_array[0] + "-" + act_code_array[1] + "-" + act_code_array[2];

    console.log(act_code+"\n"+llibre);

    console.log("data retrieved, builing URL...");

    respostes_pdf_url = "http://projecteeso.lagaleratext.cat/static/llibres/" + llibre + "/MEDIES/" + act_code + "-R.pdf";
    console.log(respostes_pdf_url);

    var solucionsWin = window.open(respostes_pdf_url, "", "location=0, scrollbars=1, status=0, menubar=0, left=10, top=500, width=100, height=100");

    solucionsWin.document.title = "Donde está el coche!";

})()
<|endoftext|>


<commit_before>// Codi del la funció "Solucions La Galera Text (Autocorrectors)"
// Autor: Marc Clascà
// (c)2013
// Visiteu pàgina oficial per instalació i més informació mclasca.sytes.net/solucions/

(function () {
    console.log("Script iniciat");

    url = document.URL;

    console.log(url);

    url_array = url.split("/");
    act_code = url_array[2];
    act_code_array = act_code.split("-");
    llibre = act_code_array[0] + "-" + act_code_array[1] + "-" + act_code_array[2];

    console.log(act_code+"\n"+llibre);

    if(!(window.open(activitat, "Apunta Aquí les solucions!"))){
    	console.log("No s'ha pogut obrir la finestra.")
    };
})()
<commit_msg>Fix #3<commit_after>// Codi del la funció "Solucions La Galera Text (Autocorrectors)"
// Autor: Marc Clascà
// (c)2013
// Visiteu pàgina oficial per instalació i més informació mclasca.sytes.net/solucions/

(function () {
    console.log("Script iniciat");

    url = document.URL;

    console.log(url);

    url_array = url.split("/");
    act_code = url_array[4];
    act_code_array = act_code.split("-");
    llibre = act_code_array[0] + "-" + act_code_array[1] + "-" + act_code_array[2];

    console.log(act_code+"\n"+llibre);

    if(!(window.open(activitat, "Apunta Aquí les solucions!"))){
    	console.log("No s'ha pogut obrir la finestra.")
    };
})()
<|endoftext|>


<commit_before>'use strict';
const mongoURI = process.env.MONGODB_URI;
var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert');

var codeCounter = (function (){
	MongoClient.connect(mongoURI , function(err, db){
		if(err){
			console.log("GET CURRCODE: OPENING", err);
		} else {
			var collection = db.collection('codeCounter');
			collection.find().toArray(function(err, res) {
				if(err){
					console.log("GET CURRCODE: READING", err);
				} else{
					console.log(res[0]);
					return(res[0].codeCounter);
					//
				}
			})
		}
	});
})();

var dict = (function(){
	fs.readFile('dict.json', 'utf8', function (err, data) {
		if(err) throw err;
		console.log(JSON.parse(data));
		return JSON.parse(data);
	})
	})()

/*
function getCurrCode(){
	return new Promise(function (resolve, reject){
		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("GET CURRCODE: OPENING", err);
				reject(err);
			} else {
					var collection = db.collection('codeCounter');
					collection.find().toArray(function(err, res) {
					if(err){
						console.log("GET CURRCODE: READING", err);
					} else{
						console.log(res[0]);
						resolve(res[0].codeCounter);
						//
					}
					})
				}
			});
	});
}*/

/*limitation, if codeCounter exceeds [n,n,n,n], where n
is dictSize, counter does not change*/
function makeNewCode(){
		//I wonder if there is going to be any async issues, if u run two of these at the same time hmm
		var isFinished = false;
		console.log("Dict");
		console.log(dict);
		console.log("codecounter");
		console.log(codeCounter);

		var curr = codeCounter.length - 1;
		codeCounter[curr]++;
		while (!isFinished && curr > 0) {
			if (codeCounter[curr] > (dict.length - 1)){
				codeCounter[curr] = 0;
				curr--;
				if (curr < 0 ){
					throw "Code Counter Overflowed, I.E Game Limit Reached";
				}
				else {
					codeCounter[curr - 1]++;
				}
			}
			else {
				isFinished = true;
			}
		}

		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("WRITING CURRCODE TO DB: ", err);
				reject(err);
			} else {
					var collection = db.collection('codeCounter');
					collection.updateOne({}, {"codeCounter" : codeCounter}, function(err, res) {
					if(err){
						console.log("WRITING CURRCODE TO DB: ", err);
					} else{
						console.log("WRITING CURRCODE TO DB: Success");
					}
					})
				}
		});

		return codeCounter;
}

/*needs the mongoURI and the local dictionary JSON file path
chose to take in mongoURI rather than get from process because this is module*/
module.exports.genCode = function(){
	console.log("Dict1");
		console.log(dict);
		console.log("codecounter2");
		console.log(codeCounter);
	return new Promise(function(resolve, reject){
		//var makeCode = makeNewCode.bind(codeCounter, dict.length);
		makeNewCode().then(function (code){
				resolve(code);
		});
	});
}
<commit_msg>Added test code<commit_after>'use strict';
const mongoURI = process.env.MONGODB_URI;
var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert');

var codeCounter = (function (){
	MongoClient.connect(mongoURI , function(err, db){
		if(err){
			console.log("GET CURRCODE: OPENING", err);
		} else {
			var collection = db.collection('codeCounter');
			collection.find().toArray(function(err, res) {
				if(err){
					console.log("GET CURRCODE: READING", err);
				} else{
					console.log(res[0]);
					return(res[0].codeCounter);
					//
				}
			})
		}
	});
})();

var dict = (function(){
	fs.readFile('dict.json', 'utf8', function (err, data) {
		if(err) throw err;
		console.log(JSON.parse(data));
		return JSON.parse(data);
	})
	})()

/*
function getCurrCode(){
	return new Promise(function (resolve, reject){
		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("GET CURRCODE: OPENING", err);
				reject(err);
			} else {
					var collection = db.collection('codeCounter');
					collection.find().toArray(function(err, res) {
					if(err){
						console.log("GET CURRCODE: READING", err);
					} else{
						console.log(res[0]);
						resolve(res[0].codeCounter);
						//
					}
					})
				}
			});
	});
}*/

/*limitation, if codeCounter exceeds [n,n,n,n], where n
is dictSize, counter does not change*/
function makeNewCode(){
		//I wonder if there is going to be any async issues, if u run two of these at the same time hmm
		var isFinished = false;
		console.log("Dict");
		console.log(dict);
		console.log("codecounter");
		console.log(codeCounter);

		var curr = codeCounter.length - 1;
		codeCounter[curr]++;
		while (!isFinished && curr > 0) {
			if (codeCounter[curr] > (dict.length - 1)){
				codeCounter[curr] = 0;
				curr--;
				if (curr < 0 ){
					throw "Code Counter Overflowed, I.E Game Limit Reached";
				}
				else {
					codeCounter[curr - 1]++;
				}
			}
			else {
				isFinished = true;
			}
		}

		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("WRITING CURRCODE TO DB: ", err);
				reject(err);
			} else {
					var collection = db.collection('codeCounter');
					collection.updateOne({}, {"codeCounter" : codeCounter}, function(err, res) {
					if(err){
						console.log("WRITING CURRCODE TO DB: ", err);
					} else{
						console.log("WRITING CURRCODE TO DB: Success");
					}
					})
				}
		});

		return codeCounter;
}

/*needs the mongoURI and the local dictionary JSON file path
chose to take in mongoURI rather than get from process because this is module*/
module.exports.genCode = function(){
	console.log("Dict1");
		console.log(dict);
		console.log("codecounter2");
		console.log(codeCounter);
	return new Promise(function(resolve, reject){
		//var makeCode = makeNewCode.bind(codeCounter, dict.length);
		makeNewCode().then(function (code){
				resolve(code);
		});
	});
}

module.exports.codeCounter = codeCounter;
module.exports.dict = dict;<|endoftext|>


<commit_before>'use strict';

const mongoURI = process.env.MONGODB_URI;

var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert'),
	dict = "dict";

var codeCounter = (function(){
	MongoClient.connect(mongoURI , function(err, db){
		if(err){
			console.log("GET CURRCODE: OPENING", err);
		} else {
			var collection = db.collection('codeCounter');
			collection.find().toArray(function(err, res) {
				if(err){
					console.log("GET CURRCODE: READING", err);
				} else{
					console.log(res[0]);
					return JSON.parse(JSON.stringify(res[0].codeCounter));
				}
			})
		}
	});
})();

var dict = (function(){
	fs.readFile('dict.json', 'utf8', function (err, data) {
		if(err) throw err;
		console.log(JSON.parse(data));
		return JSON.parse(data);
	});
})();

/*limitation, if codeCounter exceeds [n,n,n,n], where n
is dictSize, counter does not change*/
var makeNewCode = function(){
		//I wonder if there is going to be any async issues, if u run two of these at the same time hmm
		var isFinished = false;
		var curr = codeCounter.length - 1;
		console.log("makeNewCode");
		console.log(this);
		codeCounter[curr]++;
		while (!isFinished && curr > 0) {
			if (codeCounter[curr] > (dict.length - 1)){
				codeCounter[curr] = 0;
				curr--;
				if (curr < 0 ){
					throw "Code Counter Overflowed, I.E Game Limit Reached";
				}
				else {
					codeCounter[curr - 1]++;
				}
			}
			else {
				isFinished = true;
			}
		}

		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("WRITING CURRCODE TO DB: ", err);
			} else {
					var collection = db.collection('codeCounter');
					collection.updateOne({}, {"codeCounter" : codeCounter}, function(err, res) {
					if(err){
						console.log("WRITING CURRCODE TO DB: ", err);
					} else{
						console.log("WRITING CURRCODE TO DB: Success");
					}
					})
				}
		});

		var retv = dict[3] + dict[2] + dict[1] + dict[0];
		return codeCounter;
}

module.exports.genCode = function (){
	console.log("genCode");
	console.log("fs: " + fs);
	console.log("codeCounter: " + codeCounter);

	return new Promise(function(resolve, reject){
		//var makeCode = makeNewCode.bind(codeCounter, dict.length);
		resolve(makeNewCode());
	});
};

module.exports.decodeCode = function (code){
	return code;
}<commit_msg>fixed tony's mistake<commit_after>'use strict';

const mongoURI = process.env.MONGODB_URI;

var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert'),
	dict = "dict";

var codeCounter = (function(){
	MongoClient.connect(mongoURI , function(err, db){
		if(err){
			console.log("GET CURRCODE: OPENING", err);
		} else {
			var collection = db.collection('codeCounter');
			collection.find().toArray(function(err, res) {
				if(err){
					console.log("GET CURRCODE: READING", err);
				} else{
					console.log(res[0]);
					return JSON.parse(JSON.stringify(res[0].codeCounter));
				}
			})
		}
	});
})();

var dict = (function(){
	fs.readFile('dict.json', 'utf8', function (err, data) {
		if(err) throw err;
		console.log(JSON.parse(data));
		return JSON.parse(data);
	});
})();

/*limitation, if codeCounter exceeds [n,n,n,n], where n
is dictSize, counter does not change*/
var makeNewCode = function(){
		//I wonder if there is going to be any async issues, if u run two of these at the same time hmm
		var isFinished = false;
		var curr = codeCounter.length - 1;
		console.log("makeNewCode");
		console.log(this);
		codeCounter[curr]++;
		while (!isFinished && curr > 0) {
			if (codeCounter[curr] > (dict.length - 1)){
				codeCounter[curr] = 0;
				curr--;
				if (curr < 0 ){
					throw "Code Counter Overflowed, I.E Game Limit Reached";
				}
				else {
					codeCounter[curr - 1]++;
				}
			}
			else {
				isFinished = true;
			}
		}

		MongoClient.connect(mongoURI , function(err, db){
			if(err){
				console.log("WRITING CURRCODE TO DB: ", err);
			} else {
					var collection = db.collection('codeCounter');
					collection.updateOne({}, {"codeCounter" : codeCounter}, function(err, res) {
					if(err){
						console.log("WRITING CURRCODE TO DB: ", err);
					} else{
						console.log("WRITING CURRCODE TO DB: Success");
					}
					})
				}
		});

		var retv = dict[3] + dict[2] + dict[1] + dict[0];
		return retv;
}

module.exports.genCode = function (){
	console.log("genCode");
	console.log("fs: " + fs);
	console.log("codeCounter: " + codeCounter);

	return new Promise(function(resolve, reject){
		//var makeCode = makeNewCode.bind(codeCounter, dict.length);
		resolve(makeNewCode());
	});
};

module.exports.decodeCode = function (code){
	return code;
}<|endoftext|>


<commit_before>var exampleLoc = new google.maps.LatLng(-25.363882,131.044922);

var map = new google.maps.Map(document.getElementById('map'),{
  zoom: 4,
  center: exampleLoc,
  disableDefaultUI: true,
  zoomControl: true,
  zoomControlOptions: {
    style: google.maps.ZoomControlStyle.SMALL
  },
  mapTypeId: google.maps.MapTypeId.ROADMAP
});

var infowindow = new google.maps.InfoWindow({
    content: 'Hello, world!'
});

var marker = new google.maps.Marker({
    position: exampleLoc,
    map: map
});

google.maps.event.addListener(marker, 'click', function() {
  infowindow.open(map,marker);
});

var circle = new google.maps.Circle({
  strokeColor: '#FF0000',
  strokeOpacity: 0.8,
  strokeWeight: 3,
  fillColor: '#FF0000',
  fillOpacity: 0.1,
  map: map,
  center: exampleLoc,
  radius: 400000
});

var exampleKml = new google.maps.KmlLayer({
  url: 'http://ahocevar.github.io/gmaps-to-ol3-demo/data/example.kml',
  clickable: false
});
exampleKml.setMap(map);

<commit_msg>Adding credits to gmaps example<commit_after>/*
 * This is a slightly modified version of the Google Maps v3 example from
 * https://www.mapbox.com/blog/switching-to-mapboxjs/
 */

var exampleLoc = new google.maps.LatLng(-25.363882,131.044922);

var map = new google.maps.Map(document.getElementById('map'),{
  zoom: 4,
  center: exampleLoc,
  disableDefaultUI: true,
  zoomControl: true,
  zoomControlOptions: {
    style: google.maps.ZoomControlStyle.SMALL
  },
  mapTypeId: google.maps.MapTypeId.ROADMAP
});

var infowindow = new google.maps.InfoWindow({
    content: 'Hello, world!'
});

var marker = new google.maps.Marker({
    position: exampleLoc,
    map: map
});

google.maps.event.addListener(marker, 'click', function() {
  infowindow.open(map,marker);
});

var circle = new google.maps.Circle({
  strokeColor: '#FF0000',
  strokeOpacity: 0.8,
  strokeWeight: 3,
  fillColor: '#FF0000',
  fillOpacity: 0.1,
  map: map,
  center: exampleLoc,
  radius: 400000
});

var exampleKml = new google.maps.KmlLayer({
  url: 'http://ahocevar.github.io/gmaps-to-ol3-demo/data/example.kml',
  clickable: false
});
exampleKml.setMap(map);

<|endoftext|>


<commit_before>'use strict'

const Desktop = require('../core/app.js')
const pkg = require('../package.json')

const { debounce, pick } = require('lodash')
const path = require('path')
const os = require('os')

const proxy = require('./js/proxy')
const { COZY_CLIENT_REVOKED_MESSAGE } = require('../core/remote/cozy')
const migrations = require('../core/pouch/migrations')
const config = require('../core/config')

const autoLaunch = require('./js/autolaunch')
const lastFiles = require('./js/lastfiles')
const tray = require('./js/tray')
const TrayWM = require('./js/tray.window.js')
const UpdaterWM = require('./js/updater.window.js')
const HelpWM = require('./js/help.window.js')
const OnboardingWM = require('./js/onboarding.window.js')

const { selectIcon } = require('./js/fileutils')
const { buildAppMenu } = require('./js/appmenu')
const i18n = require('./js/i18n')
const { translate } = i18n
const { incompatibilitiesErrorMessage } = require('./js/incompatibilitiesmsg')
const UserActionRequiredDialog = require('./js/components/UserActionRequiredDialog')
const { app, Menu, Notification, ipcMain, dialog } = require('electron')

const DAILY = 3600 * 24 * 1000

// FIXME: https://github.com/electron/electron/issues/10864
if (process.platform === 'win32') app.setAppUserModelId('io.cozy.desktop')

const log = Desktop.logger({
  component: 'GUI'
})
process.on('uncaughtException', err => log.error(err))

const mainInstance = app.requestSingleInstanceLock()
if (!mainInstance && !process.env.COZY_DESKTOP_PROPERTY_BASED_TESTING) {
  log.warn('Cozy Drive is already running. Exiting...')
  app.exit()
}

let desktop
let state = 'not-configured'
let errorMessage = ''
let userActionRequired = null
let diskTimeout = null
let onboardingWindow = null
let helpWindow = null
let updaterWindow = null
let trayWindow = null

const toggleWindow = bounds => {
  if (trayWindow.shown()) trayWindow.hide()
  else showWindow(bounds)
}

// @TODO facto with showWindow after making args clear with tray position
const showWindowStartApp = () => {
  if (!desktop.config.syncPath) {
    onboardingWindow.show()
    // registration is done, but we need a syncPath
    if (desktop.config.isValid()) {
      onboardingWindow.jumpToSyncPath()
    }
  } else {
    startSync()
  }
}

const showWindow = bounds => {
  if (revokedAlertShown || syncDirUnlinkedShown) return
  if (updaterWindow && updaterWindow.shown()) return updaterWindow.focus()
  if (!desktop.config.syncPath) {
    onboardingWindow.show(bounds)
    // registration is done, but we need a syncPath
    if (desktop.config.isValid()) {
      onboardingWindow.jumpToSyncPath()
    }
  } else {
    trayWindow.show(bounds).then(() => startSync())
  }
}

let revokedAlertShown = false
let syncDirUnlinkedShown = false
let invalidConfigShown = false

const sendErrorToMainWindow = msg => {
  if (msg === COZY_CLIENT_REVOKED_MESSAGE) {
    if (revokedAlertShown) return
    revokedAlertShown = true // prevent the alert from appearing twice
    const options = {
      type: 'warning',
      title: pkg.productName,
      message: translate(
        'Revoked Synchronization with your Cozy is unavailable, maybe you revoked this computer?'
      ),
      detail: translate(
        "Revoked In case you didn't, contact us at contact@cozycloud.cc"
      ),
      buttons: [
        translate('Revoked Log out'),
        translate('Revoked Try again later')
      ],
      defaultId: 1
    }
    trayWindow.hide()
    const userChoice = dialog.showMessageBox(null, options)
    if (userChoice === 0) {
      desktop
        .stopSync()
        .then(() => desktop.removeConfig())
        .then(() => log.info('removed'))
        .then(() => trayWindow.doRestart())
        .catch(err => log.error(err))
    } else {
      app.quit()
    }
    return // no notification
  } else if (msg === 'Syncdir has been unlinked') {
    if (syncDirUnlinkedShown) return
    syncDirUnlinkedShown = true // prevent the alert from appearing twice
    const options = {
      type: 'warning',
      title: translate('SyncDirUnlinked Title'),
      message: translate('SyncDirUnlinked You have removed your sync dir.'),
      detail: translate('SyncDirUnlinked The client will restart'),
      buttons: [translate('SyncDirUnlinked Choose Folder')],
      cancelId: 0,
      defaultId: 0
    }
    trayWindow.hide()
    dialog.showMessageBox(null, options)
    desktop
      .stopSync()
      .then(() => desktop.pouch.db.destroy())
      .then(() => {
        desktop.config.syncPath = undefined
      })
      .then(() => desktop.config.persist())
      .then(() => log.info('removed'))
      .then(() => trayWindow.doRestart())
      .catch(err => log.error(err))
    return // no notification
  } else if (msg === config.INVALID_CONFIG_ERROR) {
    msg = translate('InvalidConfiguration Invalid configuration')
    trayWindow.send('sync-error', msg)

    if (invalidConfigShown) return
    invalidConfigShown = true // prevent the alert from appearing twice

    const options = {
      type: 'warning',
      title: translate('InvalidConfiguration Invalid configuration'),
      message: translate(
        'InvalidConfiguration The client configuration is invalid'
      ),
      detail: translate(
        'InvalidConfiguration Please log out and go through the onboarding again or contact us at contact@cozycloud.cc'
      ),
      buttons: [
        translate('InvalidConfiguration Log out'),
        translate('InvalidConfiguration Contact support')
      ],
      defaultId: 0
    }
    trayWindow.hide()
    const userChoice = dialog.showMessageBox(null, options)
    if (userChoice === 0) {
      desktop
        .removeConfig()
        .then(() => log.info('removed'))
        .then(() => trayWindow.doRestart())
        .catch(err => log.error(err))
    } else {
      helpWindow.show()
    }
    return // no notification
  } else if (msg === 'Cozy is full' || msg === 'No more disk space') {
    msg = translate('Error ' + msg)
    trayWindow.send('sync-error', msg)
  } else if (msg === 'Syncdir is empty') {
    trayWindow.send('sync-error', translate('SyncDirEmpty Title'))
    const options = {
      type: 'warning',
      title: translate('SyncDirEmpty Title'),
      message: translate('SyncDirEmpty Message'),
      detail: translate('SyncDirEmpty Detail')
    }
    dialog.showMessageBox(null, options)
    desktop.stopSync().catch(err => log.error(err))
    return // no notification
  } else if (msg === migrations.MIGRATION_RESULT_FAILED) {
    desktop.stopSync().catch(err => log.error(err))
    msg = translate('Dashboard App upgrade failed')
    trayWindow.send('sync-error', msg)
  } else {
    msg = translate('Dashboard Synchronization incomplete')
    trayWindow.send('sync-error', msg)
  }
  const notif = new Notification({ title: 'Cozy Drive', body: msg })
  notif.show()
}

const updateState = (newState, data) => {
  if (newState === 'error') errorMessage = data
  if (newState === 'online' && state !== 'offline') return
  if (newState === 'offline' && state === 'error') return

  // Update window status bar
  if (newState === 'online') {
    trayWindow.send('up-to-date')
  } else if (newState === 'offline') {
    trayWindow.send('offline')
  } else if (newState === 'error') {
    sendErrorToMainWindow(data)
  } else if (newState === 'sync-status') {
    trayWindow.send('sync-status', data)
  } else if (newState === 'syncing' && data && data.filename) {
    trayWindow.send('transfer', data)
  }

  if (newState === 'sync-status') {
    state = data.label === 'uptodate' ? 'up-to-date' : 'syncing'
  } else if (newState === 'uptodate') {
    state = 'up-to-date'
  } else {
    state = newState
  }
  // Update systray icon and tooltip
  tray.setState(state, data)
}

const addFile = info => {
  const file = {
    filename: path.basename(info.path),
    path: info.path,
    icon: selectIcon(info),
    size: info.size || 0,
    updated: +new Date()
  }
  updateState('syncing', file)
  lastFiles.add(file)
  lastFiles.persists()
}

const removeFile = info => {
  const file = {
    filename: path.basename(info.path),
    path: info.path,
    icon: '',
    size: 0,
    updated: 0
  }
  updateState('syncing')
  trayWindow.send('delete-file', file)
  lastFiles.remove(file)
  lastFiles.persists()
}

const sendDiskUsage = () => {
  if (diskTimeout) {
    clearTimeout(diskTimeout)
    diskTimeout = null
  }
  if (trayWindow) {
    diskTimeout = setTimeout(sendDiskUsage, 10 * 60 * 1000) // every 10 minutes
    desktop.diskUsage().then(
      res => {
        const space = {
          used: +res.attributes.used,
          quota: +(res.attributes.quota || 0)
        }
        trayWindow.send('disk-space', space)
      },
      err => log.error(err)
    )
  }
}

const startSync = force => {
  trayWindow.send(
    'synchronization',
    desktop.config.cozyUrl,
    desktop.config.deviceName
  )
  for (let file of lastFiles.list()) {
    trayWindow.send('transfer', file)
  }
  if (desktop.sync && !force) {
    if (userActionRequired) {
      trayWindow.send('user-action-required', userActionRequired)
    } else if (state === 'up-to-date' || state === 'online') {
      trayWindow.send('up-to-date')
    } else if (state === 'offline') {
      trayWindow.send('offline')
    } else if (state === 'error') {
      sendErrorToMainWindow(errorMessage)
    }
    sendDiskUsage()
  } else {
    updateState('syncing')
    desktop.events.on('sync-status', status => {
      updateState('sync-status', status)
    })
    desktop.events.on('online', () => {
      updateState('online')
    })
    desktop.events.on('offline', () => {
      updateState('offline')
    })
    desktop.events.on('remoteWarnings', warnings => {
      if (warnings.length > 0) {
        trayWindow.send('remoteWarnings', warnings)
      } else if (userActionRequired) {
        log.info('User action complete.')
        trayWindow.doRestart()
      }
    })
    desktop.events.on('transfer-started', addFile)
    desktop.events.on('transfer-copy', addFile)
    desktop.events.on('transfer-move', (info, old) => {
      addFile(info)
      removeFile(old)
    })
    const notifyIncompatibilities = debounce(
      incompatibilities => {
        sendErrorToMainWindow(incompatibilitiesErrorMessage(incompatibilities))
      },
      5000,
      { leading: true }
    )
    desktop.events.on('platform-incompatibilities', incompatibilitiesList => {
      incompatibilitiesList.forEach(incompatibilities => {
        notifyIncompatibilities(incompatibilities)
      })
    })
    desktop.events.on('syncdir-unlinked', () => {
      sendErrorToMainWindow('Syncdir has been unlinked')
    })
    desktop.events.on('delete-file', removeFile)

    try {
      desktop.setup()
    } catch (err) {
      log.fatal({ err, sentry: true }, 'Could not setup app')
      if (err instanceof config.InvalidConfigError) {
        updateState('error', err.name)
      } else {
        updateState('error', err.message)
      }
      return
    }

    desktop
      .startSync(desktop.config.fileConfig.mode)
      .then(() => sendErrorToMainWindow('stopped'))
      .catch(err => {
        if (err.status === 402) {
          // Only show notification popup on the first check (the GUI will
          // include a warning anyway).
          if (!userActionRequired) UserActionRequiredDialog.show(err)

          userActionRequired = pick(err, [
            'title',
            'code',
            'detail',
            'links',
            'message'
          ])
          trayWindow.send('user-action-required', userActionRequired)
          desktop.remote.warningsPoller.switchMode('medium')
          return
        }

        const msg =
          err instanceof migrations.MigrationFailedError
            ? err.name
            : err.message
        updateState('error', msg)
        sendDiskUsage()
      })
    sendDiskUsage()
  }
  autoLaunch.isEnabled().then(enabled => {
    trayWindow.send('auto-launch', enabled)
  })
}

const dumbhash = k =>
  k.split('').reduce((a, c) => ((a << 5) - a + c.charCodeAt(0)) | 0)

app.on('second-instance', () => {
  showWindow()
})

app.on('ready', () => {
  // Once configured and running in the tray, the app doesn't need to be
  // visible anymore in macOS dock (and cmd+tab), even when the tray popover
  // is visible, until another window shows up.
  if (process.platform === 'darwin') app.dock.hide()

  const { session } = require('electron')

  const hostID = (dumbhash(os.hostname()) % 4096).toString(16)
  let userAgent = `Cozy-Desktop-${process.platform}-${pkg.version}-${hostID}`
  proxy.setup(app, proxy.config(), session, userAgent, () => {
    log.info('Loading CLI...')
    i18n.init(app)
    try {
      desktop = new Desktop.App(process.env.COZY_DESKTOP_DIR)
    } catch (err) {
      if (err.message.match(/GLIBCXX/)) {
        dialog.showMessageBox({
          type: 'error',
          message: translate('Error Bad GLIBCXX version')
        })
        app.quit()
        return
      } else throw err
    }
    tray.init(app, toggleWindow)
    lastFiles.init(desktop)
    log.trace('Setting up tray WM...')
    trayWindow = new TrayWM(app, desktop)
    log.trace('Setting up help WM...')
    helpWindow = new HelpWM(app, desktop)
    log.trace('Setting up onboarding WM...')
    onboardingWindow = new OnboardingWM(app, desktop)
    onboardingWindow.onOnboardingDone(() => {
      onboardingWindow.hide()
      trayWindow.show().then(() => startSync())
    })
    if (app.isPackaged) {
      log.trace('Setting up updater WM...')
      updaterWindow = new UpdaterWM(app, desktop)
      updaterWindow.onUpToDate(() => {
        updaterWindow.hide()
        showWindowStartApp()
      })
      updaterWindow.checkForUpdates()
      setInterval(() => {
        updaterWindow.checkForUpdates()
      }, DAILY)
    } else {
      showWindowStartApp()
    }

    // Os X wants all application to have a menu
    Menu.setApplicationMenu(buildAppMenu(app))

    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    app.on('activate', showWindow)
  })
})

// Don't quit the app when all windows are closed, keep the tray icon
// See http://electron.atom.io/docs/api/app/#event-window-all-closed
app.on('window-all-closed', () => {
  log.debug('All windows closed. Keep running in tray...')
})

ipcMain.on('show-help', () => {
  helpWindow.show()
})

ipcMain.on('userActionInProgress', () => {
  desktop.remote.warningsPoller.switchMode('fast')
})

// On watch mode, automatically reload the window when sources are updated
if (process.env.WATCH === 'true') {
  const chokidar = require('chokidar')
  chokidar.watch(['*.{html,js,css}'], { cwd: __dirname }).on('change', () => {
    if (updaterWindow) {
      updaterWindow.reload()
    } else if (trayWindow) {
      trayWindow.reload()
    }
  })
}
<commit_msg>gui/main: Update systray state before window state<commit_after>'use strict'

const Desktop = require('../core/app.js')
const pkg = require('../package.json')

const { debounce, pick } = require('lodash')
const path = require('path')
const os = require('os')

const proxy = require('./js/proxy')
const { COZY_CLIENT_REVOKED_MESSAGE } = require('../core/remote/cozy')
const migrations = require('../core/pouch/migrations')
const config = require('../core/config')

const autoLaunch = require('./js/autolaunch')
const lastFiles = require('./js/lastfiles')
const tray = require('./js/tray')
const TrayWM = require('./js/tray.window.js')
const UpdaterWM = require('./js/updater.window.js')
const HelpWM = require('./js/help.window.js')
const OnboardingWM = require('./js/onboarding.window.js')

const { selectIcon } = require('./js/fileutils')
const { buildAppMenu } = require('./js/appmenu')
const i18n = require('./js/i18n')
const { translate } = i18n
const { incompatibilitiesErrorMessage } = require('./js/incompatibilitiesmsg')
const UserActionRequiredDialog = require('./js/components/UserActionRequiredDialog')
const { app, Menu, Notification, ipcMain, dialog } = require('electron')

const DAILY = 3600 * 24 * 1000

// FIXME: https://github.com/electron/electron/issues/10864
if (process.platform === 'win32') app.setAppUserModelId('io.cozy.desktop')

const log = Desktop.logger({
  component: 'GUI'
})
process.on('uncaughtException', err => log.error(err))

const mainInstance = app.requestSingleInstanceLock()
if (!mainInstance && !process.env.COZY_DESKTOP_PROPERTY_BASED_TESTING) {
  log.warn('Cozy Drive is already running. Exiting...')
  app.exit()
}

let desktop
let state = 'not-configured'
let errorMessage = ''
let userActionRequired = null
let diskTimeout = null
let onboardingWindow = null
let helpWindow = null
let updaterWindow = null
let trayWindow = null

const toggleWindow = bounds => {
  if (trayWindow.shown()) trayWindow.hide()
  else showWindow(bounds)
}

// @TODO facto with showWindow after making args clear with tray position
const showWindowStartApp = () => {
  if (!desktop.config.syncPath) {
    onboardingWindow.show()
    // registration is done, but we need a syncPath
    if (desktop.config.isValid()) {
      onboardingWindow.jumpToSyncPath()
    }
  } else {
    startSync()
  }
}

const showWindow = bounds => {
  if (revokedAlertShown || syncDirUnlinkedShown) return
  if (updaterWindow && updaterWindow.shown()) return updaterWindow.focus()
  if (!desktop.config.syncPath) {
    onboardingWindow.show(bounds)
    // registration is done, but we need a syncPath
    if (desktop.config.isValid()) {
      onboardingWindow.jumpToSyncPath()
    }
  } else {
    trayWindow.show(bounds).then(() => startSync())
  }
}

let revokedAlertShown = false
let syncDirUnlinkedShown = false
let invalidConfigShown = false

const sendErrorToMainWindow = msg => {
  if (msg === COZY_CLIENT_REVOKED_MESSAGE) {
    if (revokedAlertShown) return
    revokedAlertShown = true // prevent the alert from appearing twice
    const options = {
      type: 'warning',
      title: pkg.productName,
      message: translate(
        'Revoked Synchronization with your Cozy is unavailable, maybe you revoked this computer?'
      ),
      detail: translate(
        "Revoked In case you didn't, contact us at contact@cozycloud.cc"
      ),
      buttons: [
        translate('Revoked Log out'),
        translate('Revoked Try again later')
      ],
      defaultId: 1
    }
    trayWindow.hide()
    const userChoice = dialog.showMessageBox(null, options)
    if (userChoice === 0) {
      desktop
        .stopSync()
        .then(() => desktop.removeConfig())
        .then(() => log.info('removed'))
        .then(() => trayWindow.doRestart())
        .catch(err => log.error(err))
    } else {
      app.quit()
    }
    return // no notification
  } else if (msg === 'Syncdir has been unlinked') {
    if (syncDirUnlinkedShown) return
    syncDirUnlinkedShown = true // prevent the alert from appearing twice
    const options = {
      type: 'warning',
      title: translate('SyncDirUnlinked Title'),
      message: translate('SyncDirUnlinked You have removed your sync dir.'),
      detail: translate('SyncDirUnlinked The client will restart'),
      buttons: [translate('SyncDirUnlinked Choose Folder')],
      cancelId: 0,
      defaultId: 0
    }
    trayWindow.hide()
    dialog.showMessageBox(null, options)
    desktop
      .stopSync()
      .then(() => desktop.pouch.db.destroy())
      .then(() => {
        desktop.config.syncPath = undefined
      })
      .then(() => desktop.config.persist())
      .then(() => log.info('removed'))
      .then(() => trayWindow.doRestart())
      .catch(err => log.error(err))
    return // no notification
  } else if (msg === config.INVALID_CONFIG_ERROR) {
    msg = translate('InvalidConfiguration Invalid configuration')
    trayWindow.send('sync-error', msg)

    if (invalidConfigShown) return
    invalidConfigShown = true // prevent the alert from appearing twice

    const options = {
      type: 'warning',
      title: translate('InvalidConfiguration Invalid configuration'),
      message: translate(
        'InvalidConfiguration The client configuration is invalid'
      ),
      detail: translate(
        'InvalidConfiguration Please log out and go through the onboarding again or contact us at contact@cozycloud.cc'
      ),
      buttons: [
        translate('InvalidConfiguration Log out'),
        translate('InvalidConfiguration Contact support')
      ],
      defaultId: 0
    }
    trayWindow.hide()
    const userChoice = dialog.showMessageBox(null, options)
    if (userChoice === 0) {
      desktop
        .removeConfig()
        .then(() => log.info('removed'))
        .then(() => trayWindow.doRestart())
        .catch(err => log.error(err))
    } else {
      helpWindow.show()
    }
    return // no notification
  } else if (msg === 'Cozy is full' || msg === 'No more disk space') {
    msg = translate('Error ' + msg)
    trayWindow.send('sync-error', msg)
  } else if (msg === 'Syncdir is empty') {
    trayWindow.send('sync-error', translate('SyncDirEmpty Title'))
    const options = {
      type: 'warning',
      title: translate('SyncDirEmpty Title'),
      message: translate('SyncDirEmpty Message'),
      detail: translate('SyncDirEmpty Detail')
    }
    dialog.showMessageBox(null, options)
    desktop.stopSync().catch(err => log.error(err))
    return // no notification
  } else if (msg === migrations.MIGRATION_RESULT_FAILED) {
    desktop.stopSync().catch(err => log.error(err))
    msg = translate('Dashboard App upgrade failed')
    trayWindow.send('sync-error', msg)
  } else {
    msg = translate('Dashboard Synchronization incomplete')
    trayWindow.send('sync-error', msg)
  }
  const notif = new Notification({ title: 'Cozy Drive', body: msg })
  notif.show()
}

const updateState = (newState, data) => {
  if (newState === 'error') errorMessage = data
  if (newState === 'online' && state !== 'offline') return
  if (newState === 'offline' && state === 'error') return

  if (newState === 'online') {
    tray.setState('up-to-date')
    trayWindow.send('up-to-date')
  } else if (newState === 'offline') {
    tray.setState('offline')
    trayWindow.send('offline')
  } else if (newState === 'error') {
    tray.setState('error', data)
    sendErrorToMainWindow(data)
  } else if (newState === 'sync-status') {
    tray.setState(data.label === 'uptodate' ? 'up-to-date' : 'syncing')
    trayWindow.send('sync-status', data)
  } else if (newState === 'syncing' && data && data.filename) {
    tray.setState('syncing', data)
    trayWindow.send('transfer', data)
  }

  if (newState === 'sync-status') {
    state = data.label === 'uptodate' ? 'up-to-date' : 'syncing'
  } else {
    state = newState
  }
}

const addFile = info => {
  const file = {
    filename: path.basename(info.path),
    path: info.path,
    icon: selectIcon(info),
    size: info.size || 0,
    updated: +new Date()
  }
  updateState('syncing', file)
  lastFiles.add(file)
  lastFiles.persists()
}

const removeFile = info => {
  const file = {
    filename: path.basename(info.path),
    path: info.path,
    icon: '',
    size: 0,
    updated: 0
  }
  updateState('syncing')
  trayWindow.send('delete-file', file)
  lastFiles.remove(file)
  lastFiles.persists()
}

const sendDiskUsage = () => {
  if (diskTimeout) {
    clearTimeout(diskTimeout)
    diskTimeout = null
  }
  if (trayWindow) {
    diskTimeout = setTimeout(sendDiskUsage, 10 * 60 * 1000) // every 10 minutes
    desktop.diskUsage().then(
      res => {
        const space = {
          used: +res.attributes.used,
          quota: +(res.attributes.quota || 0)
        }
        trayWindow.send('disk-space', space)
      },
      err => log.error(err)
    )
  }
}

const startSync = force => {
  trayWindow.send(
    'synchronization',
    desktop.config.cozyUrl,
    desktop.config.deviceName
  )
  for (let file of lastFiles.list()) {
    trayWindow.send('transfer', file)
  }
  if (desktop.sync && !force) {
    if (userActionRequired) {
      trayWindow.send('user-action-required', userActionRequired)
    } else if (state === 'up-to-date' || state === 'online') {
      trayWindow.send('up-to-date')
    } else if (state === 'offline') {
      trayWindow.send('offline')
    } else if (state === 'error') {
      sendErrorToMainWindow(errorMessage)
    }
    sendDiskUsage()
  } else {
    updateState('syncing')
    desktop.events.on('sync-status', status => {
      updateState('sync-status', status)
    })
    desktop.events.on('online', () => {
      updateState('online')
    })
    desktop.events.on('offline', () => {
      updateState('offline')
    })
    desktop.events.on('remoteWarnings', warnings => {
      if (warnings.length > 0) {
        trayWindow.send('remoteWarnings', warnings)
      } else if (userActionRequired) {
        log.info('User action complete.')
        trayWindow.doRestart()
      }
    })
    desktop.events.on('transfer-started', addFile)
    desktop.events.on('transfer-copy', addFile)
    desktop.events.on('transfer-move', (info, old) => {
      addFile(info)
      removeFile(old)
    })
    const notifyIncompatibilities = debounce(
      incompatibilities => {
        sendErrorToMainWindow(incompatibilitiesErrorMessage(incompatibilities))
      },
      5000,
      { leading: true }
    )
    desktop.events.on('platform-incompatibilities', incompatibilitiesList => {
      incompatibilitiesList.forEach(incompatibilities => {
        notifyIncompatibilities(incompatibilities)
      })
    })
    desktop.events.on('syncdir-unlinked', () => {
      sendErrorToMainWindow('Syncdir has been unlinked')
    })
    desktop.events.on('delete-file', removeFile)

    try {
      desktop.setup()
    } catch (err) {
      log.fatal({ err, sentry: true }, 'Could not setup app')
      if (err instanceof config.InvalidConfigError) {
        updateState('error', err.name)
      } else {
        updateState('error', err.message)
      }
      return
    }

    desktop
      .startSync(desktop.config.fileConfig.mode)
      .then(() => sendErrorToMainWindow('stopped'))
      .catch(err => {
        if (err.status === 402) {
          // Only show notification popup on the first check (the GUI will
          // include a warning anyway).
          if (!userActionRequired) UserActionRequiredDialog.show(err)

          userActionRequired = pick(err, [
            'title',
            'code',
            'detail',
            'links',
            'message'
          ])
          trayWindow.send('user-action-required', userActionRequired)
          desktop.remote.warningsPoller.switchMode('medium')
          return
        } else if (err instanceof migrations.MigrationFailedError) {
          updateState('error', err.name)
        } else {
          updateState('error', err.message)
        }
        sendDiskUsage()
      })
    sendDiskUsage()
  }
  autoLaunch.isEnabled().then(enabled => {
    trayWindow.send('auto-launch', enabled)
  })
}

const dumbhash = k =>
  k.split('').reduce((a, c) => ((a << 5) - a + c.charCodeAt(0)) | 0)

app.on('second-instance', () => {
  showWindow()
})

app.on('ready', () => {
  // Once configured and running in the tray, the app doesn't need to be
  // visible anymore in macOS dock (and cmd+tab), even when the tray popover
  // is visible, until another window shows up.
  if (process.platform === 'darwin') app.dock.hide()

  const { session } = require('electron')

  const hostID = (dumbhash(os.hostname()) % 4096).toString(16)
  let userAgent = `Cozy-Desktop-${process.platform}-${pkg.version}-${hostID}`
  proxy.setup(app, proxy.config(), session, userAgent, () => {
    log.info('Loading CLI...')
    i18n.init(app)
    try {
      desktop = new Desktop.App(process.env.COZY_DESKTOP_DIR)
    } catch (err) {
      if (err.message.match(/GLIBCXX/)) {
        dialog.showMessageBox({
          type: 'error',
          message: translate('Error Bad GLIBCXX version')
        })
        app.quit()
        return
      } else throw err
    }
    tray.init(app, toggleWindow)
    lastFiles.init(desktop)
    log.trace('Setting up tray WM...')
    trayWindow = new TrayWM(app, desktop)
    log.trace('Setting up help WM...')
    helpWindow = new HelpWM(app, desktop)
    log.trace('Setting up onboarding WM...')
    onboardingWindow = new OnboardingWM(app, desktop)
    onboardingWindow.onOnboardingDone(() => {
      onboardingWindow.hide()
      trayWindow.show().then(() => startSync())
    })
    if (app.isPackaged) {
      log.trace('Setting up updater WM...')
      updaterWindow = new UpdaterWM(app, desktop)
      updaterWindow.onUpToDate(() => {
        updaterWindow.hide()
        showWindowStartApp()
      })
      updaterWindow.checkForUpdates()
      setInterval(() => {
        updaterWindow.checkForUpdates()
      }, DAILY)
    } else {
      showWindowStartApp()
    }

    // Os X wants all application to have a menu
    Menu.setApplicationMenu(buildAppMenu(app))

    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    app.on('activate', showWindow)
  })
})

// Don't quit the app when all windows are closed, keep the tray icon
// See http://electron.atom.io/docs/api/app/#event-window-all-closed
app.on('window-all-closed', () => {
  log.debug('All windows closed. Keep running in tray...')
})

ipcMain.on('show-help', () => {
  helpWindow.show()
})

ipcMain.on('userActionInProgress', () => {
  desktop.remote.warningsPoller.switchMode('fast')
})

// On watch mode, automatically reload the window when sources are updated
if (process.env.WATCH === 'true') {
  const chokidar = require('chokidar')
  chokidar.watch(['*.{html,js,css}'], { cwd: __dirname }).on('change', () => {
    if (updaterWindow) {
      updaterWindow.reload()
    } else if (trayWindow) {
      trayWindow.reload()
    }
  })
}
<|endoftext|>


<commit_before>/**
 * Provides tasks for [Gulp.js](http://gulpjs.com) build system.
 * @module gulpfile
 */
'use strict';

// Module dependencies.
const child = require('child_process');
const del = require('del');
const fs = require('fs');
const gulp = require('gulp');
const path = require('path');
const plugins = require('gulp-load-plugins')();
const pkg = require('./package.json');

/**
 * Runs the default tasks.
 */
gulp.task('default', ['lint']);

/**
 * Checks the package dependencies.
 */
gulp.task('check', () => gulp.src('package.json')
  .pipe(plugins.david()).on('error', function(err) {
    console.error(err);
    this.emit('end');
  })
);

/**
 * Builds the documentation.
 */
gulp.task('doc', ['doc:assets']);

gulp.task('doc:assets', ['doc:rename'], () => gulp.src(['web/apple-touch-icon.png', 'web/favicon.ico'], {base: 'web'})
  .pipe(gulp.dest('doc/api'))
);

gulp.task('doc:build', () => {
  let command = path.join('node_modules/.bin', process.platform == 'win32' ? 'jsdoc.cmd' : 'jsdoc');
  return _exec(`${command} --configure doc/conf.json`);
});

gulp.task('doc:rename', ['doc:build'], () => new Promise((resolve, reject) =>
  fs.rename(`doc/${pkg.name}/${pkg.version}`, 'doc/api', err => {
    if(err) reject(err);
    else del(`doc/${pkg.name}`, err => err ? reject(err) : resolve());
  })
));

/**
 * Performs static analysis of source code.
 */
gulp.task('lint', () => gulp.src(['*.js', 'lib/*.js'])
  .pipe(plugins.jshint(pkg.jshintConfig))
  .pipe(plugins.jshint.reporter('default', {verbose: true}))
);

/**
 * Runs a command and prints its output.
 * @param {string} command The command to run, with space-separated arguments.
 * @return {Promise.<string>} The command output when it is finally terminated.
 * @private
 */
function _exec(command) {
  return new Promise((resolve, reject) => child.exec(command, {maxBuffer: 2 * 1024 * 1024}, (err, stdout) => {
    if(err) reject(err);
    else resolve(stdout.trim());
  }));
}
<commit_msg>Fixed a regression in the build system<commit_after>/**
 * Provides tasks for [Gulp.js](http://gulpjs.com) build system.
 * @module gulpfile
 */
'use strict';

// Module dependencies.
const child = require('child_process');
const del = require('del');
const fs = require('fs');
const gulp = require('gulp');
const path = require('path');
const plugins = require('gulp-load-plugins')();
const pkg = require('./package.json');

/**
 * Runs the default tasks.
 */
gulp.task('default', ['lint']);

/**
 * Checks the package dependencies.
 */
gulp.task('check', () => gulp.src('package.json')
  .pipe(plugins.david()).on('error', function(err) {
    console.error(err);
    this.emit('end');
  })
);

/**
 * Builds the documentation.
 */
gulp.task('doc', ['doc:assets']);

gulp.task('doc:assets', ['doc:rename'], () => gulp.src(['web/apple-touch-icon.png', 'web/favicon.ico'], {base: 'web'})
  .pipe(gulp.dest('doc/api'))
);

gulp.task('doc:build', () => {
  let command = path.join('node_modules/.bin', process.platform == 'win32' ? 'jsdoc.cmd' : 'jsdoc');
  return _exec(`${command} --configure doc/conf.json`);
});

gulp.task('doc:rename', ['doc:build'], () => new Promise((resolve, reject) =>
  fs.rename(`doc/${pkg.name}/${pkg.version}`, 'doc/api', err => {
    if(err) reject(err);
    else del(`doc/${pkg.name}`).then(resolve, reject);
  })
));

/**
 * Performs static analysis of source code.
 */
gulp.task('lint', () => gulp.src(['*.js', 'lib/*.js'])
  .pipe(plugins.jshint(pkg.jshintConfig))
  .pipe(plugins.jshint.reporter('default', {verbose: true}))
);

/**
 * Runs a command and prints its output.
 * @param {string} command The command to run, with space-separated arguments.
 * @return {Promise.<string>} The command output when it is finally terminated.
 * @private
 */
function _exec(command) {
  return new Promise((resolve, reject) => child.exec(command, {maxBuffer: 2 * 1024 * 1024}, (err, stdout) => {
    if(err) reject(err);
    else resolve(stdout.trim());
  }));
}
<|endoftext|>


<commit_before>var gulp = require('gulp'),
	path = require('path');

var to5 = require('gulp-6to5'),
	monic = require('gulp-monic'),
	gcc = require('gulp-closure-compiler'),
	rename = require('gulp-rename'),
	header = require('gulp-header'),
	replace = require('gulp-replace'),
	wrap = require('gulp-wrap'),
	bump = require('gulp-bump'),
	download = require('gulp-download'),
	istanbul = require('gulp-istanbul'),
	jasmine = require('gulp-jasmine'),
	eol = require('gulp-eol'),
	run = require('gulp-run');

function getVersion() {
	delete require.cache[require.resolve('./lib/core')];
	return require('./lib/core').VERSION.join('.');
}

function getBuilds() {
	delete require.cache[require.resolve('./builds')];
	return Object(require('./builds'));
}

gulp.task('yaspeller', function () {
	run('node node_modules/yaspeller/bin/cli.js ./').exec();
});

gulp.task('build', function (callback) {
	var builds = getBuilds(),
		i = 0;

	for (var key in builds) {
		if (!builds.hasOwnProperty(key)) {
			continue;
		}

		i++;
		var fullHead =
			'/*!\n' +
			' * Snakeskin v' + getVersion() + (key !== 'snakeskin' ? ' (' + key.replace(/^snakeskin\./, '') + ')' : '') + '\n' +
			' * https://github.com/kobezzza/Snakeskin\n' +
			' *\n' +
			' * Released under the MIT license\n' +
			' * https://github.com/kobezzza/Snakeskin/blob/master/LICENSE\n' +
			' *\n' +
			' * Date: ' + new Date().toUTCString() + '\n' +
			' */\n\n';

		gulp.src('./lib/core.js')
			.pipe(monic({
				flags: builds[key]
			}))

			.pipe(to5({
				blacklist: [
					'specPropertyLiterals',
					'specMemberExpressionLiterals',
					'undefinedToVoid'
				]
			}))

			.pipe(wrap(
				'(function (root) {' +
					'\n' +
					'<%= contents %>' +
					'\n' +
				'}).call(new Function(\'return this\')(), this);'
			))

			.pipe(replace(/\/\/\/\/#include/g, '//#include'))
			.pipe(monic())

			// Фикс @param {foo} [bar=1] -> @param {foo} [bar]
			.pipe(replace(/(@param {.*?}) \[([$\w.]+)=.*]/g, '$1 $2'))

			// Пробельные символы в строках-шаблонах
			.pipe(replace(/\/\* cbws \*\/"[\s\S]*?[^\\"]";?(?:$|[}]+$|[)]+;?$)/gm, function (sstr) {
				return sstr
					.replace(/\\n|\\t/g, '')
					.replace(/\\[\r\n]/g, ' ');
			}))

			.pipe(header(fullHead))
			.pipe(rename(key + '.js'))
			.pipe(eol())
			.pipe(gulp.dest('./dist/'))

			.on('end', function () {
				i--;

				if (!i) {
					callback();
				}
			});
	}
});

gulp.task('predefs', ['build'], function (callback) {
	download([
		'https://raw.githubusercontent.com/google/closure-compiler/master/externs/fileapi.js',
		'https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/jasmine.js'
	])
		.pipe(eol())
		.pipe(gulp.dest('./predefs/src/ws'))
		.on('end', function () {
			gulp.src('./predefs/src/index.js')
				.pipe(monic())
				.pipe(gulp.dest('./predefs/build'))
				.on('end', callback);
		});
});

gulp.task('test', ['predefs'], function (callback) {
	gulp.src('./dist/snakeskin.js')
		.pipe(istanbul())
		.pipe(istanbul.hookRequire())
		.on('finish', function () {
			gulp.src(['./test/test.dev.js'])
				.pipe(jasmine())
				.pipe(istanbul.writeReports())
				.on('end', callback);
		});
});

function compile(dev) {
	return function (callback) {
		var builds = getBuilds(),
			i = 0;

		for (var key in builds) {
			if (!builds.hasOwnProperty(key)) {
				continue;
			}

			i++;
			var params = {
				compilerPath: './bower_components/closure-compiler/compiler.jar',
				fileName: key + '.min.js',
				compilerFlags: {
					compilation_level: 'ADVANCED_OPTIMIZATIONS',
					use_types_for_optimization: null,
					language_in: 'ES5',
					externs: [
						'./predefs/build/index.js'
					]
				}
			};

			if (dev) {
				params.compilerFlags.jscomp_warning = [
					'invalidCasts',
					'accessControls',
					'checkDebuggerStatement',
					'checkRegExp',
					'checkTypes',
					'const',
					'constantProperty',
					'deprecated',
					'externsValidation',
					'missingProperties',
					'visibility'
				];
			}

			gulp.src(path.join('./dist/', key + '.js'))
				.pipe(gcc(params))
				.pipe(header('/*! Snakeskin v' + getVersion() + (key !== 'snakeskin' ? ' (' + key.replace(/^snakeskin\./, '') + ')' : '') + ' | https://github.com/kobezzza/Snakeskin/blob/master/LICENSE */\n'))
				.pipe(replace(/\(function\(.*?\)\{/, '$&\'use strict\';'))
				.pipe(eol())
				.pipe(gulp.dest('./dist/'))
				.on('end', function () {
					i--;

					if (!i) {
						callback.apply(this, arguments);
					}
				});
		}
	}
}

gulp.task('compile', ['test'], compile());
gulp.task('compile-dev', ['test'], compile(true));

gulp.task('bump', function () {
	gulp.src('./*.json')
		.pipe(bump({version: getVersion()}))
		.pipe(eol())
		.pipe(gulp.dest('./'));
});

gulp.task('watch', function () {
	gulp.watch('./lib/**/*.js', ['build']);
	gulp.watch('./lib/core.js', ['bump']);
});

gulp.task('default', ['compile', 'bump']);
<commit_msg>Доработка сборки<commit_after>var gulp = require('gulp'),
	path = require('path');

var to5 = require('gulp-6to5'),
	monic = require('gulp-monic'),
	gcc = require('gulp-closure-compiler'),
	rename = require('gulp-rename'),
	header = require('gulp-header'),
	replace = require('gulp-replace'),
	wrap = require('gulp-wrap'),
	bump = require('gulp-bump'),
	download = require('gulp-download'),
	istanbul = require('gulp-istanbul'),
	jasmine = require('gulp-jasmine'),
	eol = require('gulp-eol'),
	run = require('gulp-run');

function getVersion() {
	delete require.cache[require.resolve('./lib/core')];
	return require('./lib/core').VERSION.join('.');
}

function getBuilds() {
	delete require.cache[require.resolve('./builds')];
	return Object(require('./builds'));
}

gulp.task('yaspeller', function () {
	run('node node_modules/yaspeller/bin/cli.js ./').exec();
});

gulp.task('build', function (callback) {
	var builds = getBuilds(),
		i = 0;

	for (var key in builds) {
		if (!builds.hasOwnProperty(key)) {
			continue;
		}

		i++;
		var fullHead =
			'/*!\n' +
			' * Snakeskin v' + getVersion() + (key !== 'snakeskin' ? ' (' + key.replace(/^snakeskin\./, '') + ')' : '') + '\n' +
			' * https://github.com/kobezzza/Snakeskin\n' +
			' *\n' +
			' * Released under the MIT license\n' +
			' * https://github.com/kobezzza/Snakeskin/blob/master/LICENSE\n' +
			' *\n' +
			' * Date: ' + new Date().toUTCString() + '\n' +
			' */\n\n';

		gulp.src('./lib/core.js')
			.pipe(monic({
				flags: builds[key]
			}))

			.pipe(to5({
				blacklist: [
					'specPropertyLiterals',
					'specMemberExpressionLiterals'
				],

				optional: [
					'undefinedToVoid'
				]
			}))

			.pipe(wrap(
				'(function (root) {' +
					'\n' +
					'<%= contents %>' +
					'\n' +
				'}).call(new Function(\'return this\')(), this);'
			))

			.pipe(replace(/\/\/\/\/#include/g, '//#include'))
			.pipe(monic())

			// Фикс @param {foo} [bar=1] -> @param {foo} [bar]
			.pipe(replace(/(@param {.*?}) \[([$\w.]+)=.*]/g, '$1 $2'))

			// Пробельные символы в строках-шаблонах
			.pipe(replace(/\/\* cbws \*\/"[\s\S]*?[^\\"]";?(?:$|[}]+$|[)]+;?$)/gm, function (sstr) {
				return sstr
					.replace(/\\n|\\t/g, '')
					.replace(/\\[\r\n]/g, ' ');
			}))

			.pipe(header(fullHead))
			.pipe(rename(key + '.js'))
			.pipe(eol())
			.pipe(gulp.dest('./dist/'))

			.on('end', function () {
				i--;

				if (!i) {
					callback();
				}
			});
	}
});

gulp.task('predefs', ['build'], function (callback) {
	download([
		'https://raw.githubusercontent.com/google/closure-compiler/master/externs/fileapi.js',
		'https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/jasmine.js'
	])
		.pipe(eol())
		.pipe(gulp.dest('./predefs/src/ws'))
		.on('end', function () {
			gulp.src('./predefs/src/index.js')
				.pipe(monic())
				.pipe(gulp.dest('./predefs/build'))
				.on('end', callback);
		});
});

gulp.task('test', ['predefs'], function (callback) {
	gulp.src('./dist/snakeskin.js')
		.pipe(istanbul())
		.pipe(istanbul.hookRequire())
		.on('finish', function () {
			gulp.src(['./test/test.dev.js'])
				.pipe(jasmine())
				.pipe(istanbul.writeReports())
				.on('end', callback);
		});
});

function compile(dev) {
	return function (callback) {
		var builds = getBuilds(),
			i = 0;

		for (var key in builds) {
			if (!builds.hasOwnProperty(key)) {
				continue;
			}

			i++;
			var params = {
				compilerPath: './bower_components/closure-compiler/compiler.jar',
				fileName: key + '.min.js',
				compilerFlags: {
					compilation_level: 'ADVANCED_OPTIMIZATIONS',
					use_types_for_optimization: null,
					language_in: 'ES5',
					externs: [
						'./predefs/build/index.js'
					]
				}
			};

			if (dev) {
				params.compilerFlags.jscomp_warning = [
					'invalidCasts',
					'accessControls',
					'checkDebuggerStatement',
					'checkRegExp',
					'checkTypes',
					'const',
					'constantProperty',
					'deprecated',
					'externsValidation',
					'missingProperties',
					'visibility'
				];
			}

			gulp.src(path.join('./dist/', key + '.js'))
				.pipe(gcc(params))
				.pipe(header('/*! Snakeskin v' + getVersion() + (key !== 'snakeskin' ? ' (' + key.replace(/^snakeskin\./, '') + ')' : '') + ' | https://github.com/kobezzza/Snakeskin/blob/master/LICENSE */\n'))
				.pipe(replace(/\(function\(.*?\)\{/, '$&\'use strict\';'))
				.pipe(eol())
				.pipe(gulp.dest('./dist/'))
				.on('end', function () {
					i--;

					if (!i) {
						callback.apply(this, arguments);
					}
				});
		}
	}
}

gulp.task('compile', ['test'], compile());
gulp.task('compile-dev', ['test'], compile(true));

gulp.task('bump', function () {
	gulp.src('./*.json')
		.pipe(bump({version: getVersion()}))
		.pipe(eol())
		.pipe(gulp.dest('./'));
});

gulp.task('watch', function () {
	gulp.watch('./lib/**/*.js', ['build']);
	gulp.watch('./lib/core.js', ['bump']);
});

gulp.task('default', ['compile', 'bump']);
<|endoftext|>