<commit_before>var fs               = require('fs')
,   util             = require('util')
,   events           = require('events')
,   moment           = require('moment')
,   Melted           = require('./api/Melted')
,   StatusClip       = require('./api/StatusClip')
,   playlists_driver = require('./drivers/playlists/playlists-driver')
,   status_driver    = require('./drivers/status/pubsub')
,   utils            = require('./utils')
,   mbc              = require('mbc-common')
,   config           = mbc.config.Mosto.General
,   _                = require('underscore')
,   heartbeats       = require('./heartbeats')
,   models           = require('./models/Mosto')
,   logger           = mbc.logger().addLogger('CORE')
;
//TODO: Chequear window, se esta construyendo de formas distintas
//INCLUSO EN EL DRIVER MISMO SE USA DE FORMAS DISTINTAS!!!
function mosto(customConfig) {

    /** CONFIGURATION */
    this.config         = customConfig || config;
    this.server         = undefined;
    this.pl_driver      = undefined;
    this.status_driver  = undefined;

    /* MODULES */
    this.heartbeats = undefined;
    this.playlists  = undefined;

    /* MELTED CHECK */
    this.meltedInterval = undefined;
    /* TODO: This should be in config */
    this.restartMelted = true;

    events.EventEmitter.call(this);
}

util.inherits(mosto, events.EventEmitter);

mosto.prototype.getTimeWindow = function(from, to) {
    return {
        from: from || moment(),
        to: to || moment().add(config.min_scheduled_hours, 'hours'),
    };
};

mosto.prototype.inTimeWindow = function(obj) {
    // expects obj.start and obj.end to exist and be moment()s
    var timeWindow = this.getTimeWindow();
    logger.debug("TimeWindow: " + timeWindow.from + " - " + timeWindow.to);
    logger.debug("Playlist (id " + obj.get("id") + "): " + obj.get("start") + " - " + obj.get("end"));
    return (obj.get("end") > timeWindow.from && obj.get("start") < timeWindow.to);
};

mosto.prototype.addPlaylist = function(playlist) {
    var self = this;

    logger.debug("Adding playlist " + playlist.get("id"));
    if(self.inTimeWindow(playlist)) {
        self.playlists.addPlaylist(playlist);
    } else {
        logger.debug("Playlist " + playlist.get("id") + " was not in time window, discarding...");
    }
};

mosto.prototype.initDriver = function() {
    var self = this;
    logger.info("Initializing playlists driver");

    this.pl_driver.on('create', function(playlist) {
        logger.debug("Received create event for playlist " + playlist.get("id"));
        self.addPlaylist(playlist);
    });

    this.pl_driver.on('update', function(playlist) {
        logger.debug("Received update event for playlist " + playlist.get("id"));
        if(!self.inTimeWindow(playlist)) {
            logger.debug("Playlist " + playlist.get("id") + " was out of time window, removing...");
            self.playlists.removePlaylist(playlist);
        } else {
            logger.debug("Playlist " + playlist.get("id") + " was in time window, adding...");
            self.addPlaylist(playlist);
        }
    });

    this.pl_driver.on('delete', function(id) {
        logger.debug("Received delete event for playlist " + id);
        self.playlists.removePlaylist(id);
    });

    self.pl_driver.start();
};

mosto.prototype.stopDriver = function() {
    logger.info("Stopping playlists driver");

    this.pl_driver.stop();

    this.pl_driver.removeAllListeners("create");
    this.pl_driver.removeAllListeners("update");
    this.pl_driver.removeAllListeners("delete");
};

mosto.prototype.initHeartbeats = function() {
    var self = this;

    logger.info("Initializing heartbeats");

    self.heartbeats.on('clipStatus', function(status) {
        var media = status.media;
        var frame = status.frame;
        var melted_medias = self.playlists.get('melted_medias');
        var playlists = self.playlists.get('playlists');
        var status = {
            clip:{
                previous: undefined,
                current: undefined,
                next: undefined
            },
            show: {
                previous: undefined,
                current: undefined,
                next: undefined
            },
            position: frame,
            clips: melted_medias.toJSON(),
            status: null,
        };
        /* clips */
        var index = melted_medias.indexOf(media);
        status.clip.current = media.toJSON();

        if( index > 0 )
            status.clip.previous = melted_medias.at(index-1).toJSON();
        if( index < melted_medias.length - 1 )
            status.clip.next = melted_medias.at(index+1).toJSON();

        /* shows */
        var playlist = playlists.get(media.get('playlist_id'));
        if( playlist ) {
            index = playlists.indexOf(playlist);
            status.show.current = playlist.toJSON();
            if( index > 0 )
                status.show.previous = playlists.at(index-1).toJSON();
            if( index < playlists.length - 1 )
                status.show.next = playlists.at(index+1).toJSON();
        } else {
            // next playlist
            playlist = playlists.find(function(pl) {
                return pl.get('start') >= status.clip.current.end;
            });
            if( playlist ) {
                status.show.next = playlist.toJSON();
            }
            var ps = playlists.filter(function(pl) {
                return pl.get('end') <= status.clip.current.start;
            }).reverse();
            if( ps ) {
                status.show.previous = ps[0];
            }

            status.show.current = { id: -1, name: "INVALID" };
        }

        self.emit('status', status);
    });

    self.on('status', self.status_driver.setStatus.bind(self.status_driver));

    self.heartbeats.on("forceCheckout", function(window) {
        self.fetchPlaylists(window);
    });

    self.heartbeats.on("noClips", function() {
        var window = self.getTimeWindow();
        self.fetchPlaylists(window);
    });

    self.heartbeats.on('startPlaying', function() {
        self.emit('playing');
    });

    self.heartbeats.init();
};

mosto.prototype.fetchPlaylists = function(window) {
    //TODO: Hacer siempre el playlists.save para forzar que meta mas blank clips si hace falta
    var self = this;
    if (!window) {
        logger.error("No time window specified, making one up");
        window = this.getTimeWindow();
    }
    self.pl_driver.getPlaylists(window, function(playlists) {
        playlists.forEach(function(playlist) {
            self.playlists.get("playlists").add(playlist, {merge: true});
        });
        self.playlists.save();
    });
};

mosto.prototype.stopHeartbeats = function() {
    var self = this;

    logger.info("Stopping heartbeats");

    self.heartbeats.removeAllListeners("frameStatus");
    self.heartbeats.removeAllListeners("clipStatus");
    self.heartbeats.removeAllListeners("forceCheckout");
    self.heartbeats.removeAllListeners("noClips");
    self.heartbeats.removeAllListeners("startPlaying");

    return self.heartbeats.stop();
};

mosto.prototype.init = function(melted, callback) {
    logger.info("Init mbc-mosto... ") ;
    var self = this;
    /*
     * inicializar los drivers
     * inicializar los modelos backbone
     * inicializar los heartbeats
     * linkear los eventos entre pl driver y backbone driver
     * linkear heartbeat con status driver
     */
    function startall() {
        self.pl_driver     = new playlists_driver(self.config.playlist_server);
        self.status_driver = new status_driver();
        self.playlists     = models.Playlists();
        self.heartbeats    = new heartbeats();

        self.initDriver();
        self.initHeartbeats();

        self.fetchPlaylists({from: moment(), to: moment().add(4, 'hours')});
        if (self.restartMelted)
            self.meltedInterval = setTimeout(self.checkMelted.bind(self, self.scheduleMeltedCheck.bind(self), true), 5000);
        self.emit('started', 'Mosto has started');
        if (callback) callback();
    }

    function check_and_start() {
        self.checkMelted(startall);
    };

    if (melted !== undefined) {
        Melted = melted;
        check_and_start();
    }
    else
        Melted.take(check_and_start);

};

mosto.prototype.scheduleMeltedCheck = function() {
    this.meltedInterval = setTimeout(this.checkMelted.bind(this, this.scheduleMeltedCheck.bind(this), true), 100);
};

mosto.prototype.checkMelted = function(callback, forceLoad) {
    var self = this;
    Melted.is_running(function(running) {
        if (!running) {
            Melted.start(function(pid) {
                Melted.setup(undefined, undefined, function(result) {
                    if (forceLoad)
                        self.playlists.save();
                    if (callback)
                        callback();
                });
            });
        } else {
            if (callback)
                callback();
        }
    });
};

mosto.prototype.finish = function(callback) {
    var self = this;
    logger.info("mbc-mosto: [INFO] Finish mbc-mosto... ");
    if (self.restartMelted)
        clearTimeout(self.meltedInterval);
    this.stopDriver();
    this.playlists.get("melted_medias").write.take(function() {
        self.playlists.get("melted_medias").stopMvcpServer().fin(self.stopHeartbeats).fin(function() {
            self.playlists.get("melted_medias").write.leave();
            Melted.stop(function(pid) {
                setTimeout( function() {
                    Melted.leave();
                    if (callback) callback();
                }, 1000 );
            });
        });
    });
};

exports = module.exports = function(customConfig) {
    var mosto_server = new mosto(customConfig);
    return mosto_server;
};
/*

  mosto tiene que:

  * inicializar melted
  * inicializar los drivers
  * inicializar los modelos backbone
  * inicializar los heartbeats
  * linkear los eventos entre pl driver y backbone driver
  * linkear heartbeat con status driver

  */
<commit_msg>Fixed wrong usage of moment.add<commit_after>var fs               = require('fs')
,   util             = require('util')
,   events           = require('events')
,   moment           = require('moment')
,   Melted           = require('./api/Melted')
,   StatusClip       = require('./api/StatusClip')
,   playlists_driver = require('./drivers/playlists/playlists-driver')
,   status_driver    = require('./drivers/status/pubsub')
,   utils            = require('./utils')
,   mbc              = require('mbc-common')
,   config           = mbc.config.Mosto.General
,   _                = require('underscore')
,   heartbeats       = require('./heartbeats')
,   models           = require('./models/Mosto')
,   logger           = mbc.logger().addLogger('CORE')
;
//TODO: Chequear window, se esta construyendo de formas distintas
//INCLUSO EN EL DRIVER MISMO SE USA DE FORMAS DISTINTAS!!!
function mosto(customConfig) {

    /** CONFIGURATION */
    this.config         = customConfig || config;
    this.server         = undefined;
    this.pl_driver      = undefined;
    this.status_driver  = undefined;

    /* MODULES */
    this.heartbeats = undefined;
    this.playlists  = undefined;

    /* MELTED CHECK */
    this.meltedInterval = undefined;
    /* TODO: This should be in config */
    this.restartMelted = true;

    events.EventEmitter.call(this);
}

util.inherits(mosto, events.EventEmitter);

mosto.prototype.getTimeWindow = function(from, to) {
    var window = {
        from: from || moment(),
        to: to || moment().add('hours', config.min_scheduled_hours),
    };
    return window;
};

mosto.prototype.inTimeWindow = function(obj) {
    // expects obj.start and obj.end to exist and be moment()s
    var timeWindow = this.getTimeWindow();
    logger.debug("TimeWindow: " + timeWindow.from + " - " + timeWindow.to);
    logger.debug("Playlist (id " + obj.get("id") + "): " + obj.get("start") + " - " + obj.get("end"));
    return (obj.get("end") > timeWindow.from && obj.get("start") < timeWindow.to);
};

mosto.prototype.addPlaylist = function(playlist) {
    var self = this;

    logger.debug("Adding playlist " + playlist.get("id"));
    if(self.inTimeWindow(playlist)) {
        self.playlists.addPlaylist(playlist);
    } else {
        logger.debug("Playlist " + playlist.get("id") + " was not in time window, discarding...");
    }
};

mosto.prototype.initDriver = function() {
    var self = this;
    logger.info("Initializing playlists driver");

    this.pl_driver.on('create', function(playlist) {
        logger.debug("Received create event for playlist " + playlist.get("id"));
        self.addPlaylist(playlist);
    });

    this.pl_driver.on('update', function(playlist) {
        logger.debug("Received update event for playlist " + playlist.get("id"));
        if(!self.inTimeWindow(playlist)) {
            logger.debug("Playlist " + playlist.get("id") + " was out of time window, removing...");
            self.playlists.removePlaylist(playlist);
        } else {
            logger.debug("Playlist " + playlist.get("id") + " was in time window, adding...");
            self.addPlaylist(playlist);
        }
    });

    this.pl_driver.on('delete', function(id) {
        logger.debug("Received delete event for playlist " + id);
        self.playlists.removePlaylist(id);
    });

    self.pl_driver.start();
};

mosto.prototype.stopDriver = function() {
    logger.info("Stopping playlists driver");

    this.pl_driver.stop();

    this.pl_driver.removeAllListeners("create");
    this.pl_driver.removeAllListeners("update");
    this.pl_driver.removeAllListeners("delete");
};

mosto.prototype.initHeartbeats = function() {
    var self = this;

    logger.info("Initializing heartbeats");

    self.heartbeats.on('clipStatus', function(status) {
        var media = status.media;
        var frame = status.frame;
        var melted_medias = self.playlists.get('melted_medias');
        var playlists = self.playlists.get('playlists');
        var status = {
            clip:{
                previous: undefined,
                current: undefined,
                next: undefined
            },
            show: {
                previous: undefined,
                current: undefined,
                next: undefined
            },
            position: frame,
            clips: melted_medias.toJSON(),
            status: null,
        };
        /* clips */
        var index = melted_medias.indexOf(media);
        status.clip.current = media.toJSON();

        if( index > 0 )
            status.clip.previous = melted_medias.at(index-1).toJSON();
        if( index < melted_medias.length - 1 )
            status.clip.next = melted_medias.at(index+1).toJSON();

        /* shows */
        var playlist = playlists.get(media.get('playlist_id'));
        if( playlist ) {
            index = playlists.indexOf(playlist);
            status.show.current = playlist.toJSON();
            if( index > 0 )
                status.show.previous = playlists.at(index-1).toJSON();
            if( index < playlists.length - 1 )
                status.show.next = playlists.at(index+1).toJSON();
        } else {
            // next playlist
            playlist = playlists.find(function(pl) {
                return pl.get('start') >= status.clip.current.end;
            });
            if( playlist ) {
                status.show.next = playlist.toJSON();
            }
            var ps = playlists.filter(function(pl) {
                return pl.get('end') <= status.clip.current.start;
            }).reverse();
            if( ps ) {
                status.show.previous = ps[0];
            }

            status.show.current = { id: -1, name: "INVALID" };
        }

        self.emit('status', status);
    });

    self.on('status', self.status_driver.setStatus.bind(self.status_driver));

    self.heartbeats.on("forceCheckout", function(window) {
        self.fetchPlaylists(window);
    });

    self.heartbeats.on("noClips", function() {
        var window = self.getTimeWindow();
        self.fetchPlaylists(window);
    });

    self.heartbeats.on('startPlaying', function() {
        self.emit('playing');
    });

    self.heartbeats.init();
};

mosto.prototype.fetchPlaylists = function(window) {
    //TODO: Hacer siempre el playlists.save para forzar que meta mas blank clips si hace falta
    var self = this;
    if (!window) {
        logger.error("No time window specified, making one up");
        window = this.getTimeWindow();
    }
    self.pl_driver.getPlaylists(window, function(playlists) {
        playlists.forEach(function(playlist) {
            self.playlists.get("playlists").add(playlist, {merge: true});
        });
        self.playlists.save();
    });
};

mosto.prototype.stopHeartbeats = function() {
    var self = this;

    logger.info("Stopping heartbeats");

    self.heartbeats.removeAllListeners("frameStatus");
    self.heartbeats.removeAllListeners("clipStatus");
    self.heartbeats.removeAllListeners("forceCheckout");
    self.heartbeats.removeAllListeners("noClips");
    self.heartbeats.removeAllListeners("startPlaying");

    return self.heartbeats.stop();
};

mosto.prototype.init = function(melted, callback) {
    logger.info("Init mbc-mosto... ") ;
    var self = this;
    /*
     * inicializar los drivers
     * inicializar los modelos backbone
     * inicializar los heartbeats
     * linkear los eventos entre pl driver y backbone driver
     * linkear heartbeat con status driver
     */
    function startall() {
        self.pl_driver     = new playlists_driver(self.config.playlist_server);
        self.status_driver = new status_driver();
        self.playlists     = models.Playlists();
        self.heartbeats    = new heartbeats();

        self.initDriver();
        self.initHeartbeats();

        self.fetchPlaylists({from: moment(), to: moment().add(4, 'hours')});
        if (self.restartMelted)
            self.meltedInterval = setTimeout(self.checkMelted.bind(self, self.scheduleMeltedCheck.bind(self), true), 5000);
        self.emit('started', 'Mosto has started');
        if (callback) callback();
    }

    function check_and_start() {
        self.checkMelted(startall);
    };

    if (melted !== undefined) {
        Melted = melted;
        check_and_start();
    }
    else
        Melted.take(check_and_start);

};

mosto.prototype.scheduleMeltedCheck = function() {
    this.meltedInterval = setTimeout(this.checkMelted.bind(this, this.scheduleMeltedCheck.bind(this), true), 100);
};

mosto.prototype.checkMelted = function(callback, forceLoad) {
    var self = this;
    Melted.is_running(function(running) {
        if (!running) {
            Melted.start(function(pid) {
                Melted.setup(undefined, undefined, function(result) {
                    if (forceLoad)
                        self.playlists.save();
                    if (callback)
                        callback();
                });
            });
        } else {
            if (callback)
                callback();
        }
    });
};

mosto.prototype.finish = function(callback) {
    var self = this;
    logger.info("mbc-mosto: [INFO] Finish mbc-mosto... ");
    if (self.restartMelted)
        clearTimeout(self.meltedInterval);
    this.stopDriver();
    this.playlists.get("melted_medias").write.take(function() {
        self.playlists.get("melted_medias").stopMvcpServer().fin(self.stopHeartbeats).fin(function() {
            self.playlists.get("melted_medias").write.leave();
            Melted.stop(function(pid) {
                setTimeout( function() {
                    Melted.leave();
                    if (callback) callback();
                }, 1000 );
            });
        });
    });
};

exports = module.exports = function(customConfig) {
    var mosto_server = new mosto(customConfig);
    return mosto_server;
};
/*

  mosto tiene que:

  * inicializar melted
  * inicializar los drivers
  * inicializar los modelos backbone
  * inicializar los heartbeats
  * linkear los eventos entre pl driver y backbone driver
  * linkear heartbeat con status driver

  */
<|endoftext|>


<commit_before>(function() {

var checkDirection = true;

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name != "neutral-king-invariant") {
      checkVersion(design, name, value);
  }
}

var findKing = function(design, board) {
  var positions = design.allPositions();
  for (var i = 0; i < positions.length; i++) {
       var piece = board.getPiece(positions[i]);
       if ((piece !== null) && (piece.player == 3)) {
           return positions[i];
       }
  }
  return null;
}

var checkDirection = function(design, board, player, pos, dir, leapers, riders) {
  var p = design.navigate(1, pos, dir);
  if (p === null) return false;
  var piece = board.getPiece(p);
  if (piece !== null) {
      if (!_.isUndefined(player)) {
          if (piece.player != player) return false;
      }
      return (_.indexOf(leapers, piece.type) >= 0) || (_.indexOf(riders, piece.type) >= 0);
  }
  while (piece === null) {
      p = design.navigate(1, p, dir);
      if (p === null) return false;
      piece = board.getPiece(p);
  }
  if (!_.isUndefined(player)) {
      if (piece.player != player) return false;
  }
  return _.indexOf(riders, piece.type) >= 0;
}

var checkLeap = function(design, board, player, pos, o, d, knight) {
  var p = design.navigate(1, pos, o);
  if (p === null) return false;
  p = design.navigate(1, p, d);
  if (p === null) return false;
  var piece = board.getPiece(p);
  if (piece === null) return false;
  if (!_.isUndefined(player)) {
      if (piece.player != player) return false;
  }
  return (piece.type == knight);
}

var isAttacked = function(design, board, pos, player) {
  var pawn   = design.getPieceType("Pawn");
  var rook   = design.getPieceType("Rook");
  var knight = design.getPieceType("Knight");
  var bishop = design.getPieceType("Bishop");
  var n  = design.getDirection("n");  var w  = design.getDirection("w");
  var s  = design.getDirection("s");  var e  = design.getDirection("e");
  var nw = design.getDirection("nw"); var sw = design.getDirection("sw");
  var ne = design.getDirection("ne"); var se = design.getDirection("se");
  if (checkDirection(design, board, player, pos, n, [], [rook])) return true;
  if (checkDirection(design, board, player, pos, s,  [], [rook])) return true;
  if (checkDirection(design, board, player, pos, w,  [], [rook])) return true;
  if (checkDirection(design, board, player, pos, e,  [], [rook])) return true;
  if (checkDirection(design, board, player, pos, nw, [pawn], [bishop])) return true;
  if (checkDirection(design, board, player, pos, ne, [pawn], [bishop])) return true;
  if (checkDirection(design, board, player, pos, sw, [], [bishop])) return true;
  if (checkDirection(design, board, player, pos, se, [], [bishop])) return true;
  if (checkLeap(design, board, player, pos, n, nw, knight)) return true;
  if (checkLeap(design, board, player, pos, n, ne, knight)) return true;
  if (checkLeap(design, board, player, pos, s, sw, knight)) return true;
  if (checkLeap(design, board, player, pos, s, se, knight)) return true;
  if (checkLeap(design, board, player, pos, w, nw, knight)) return true;
  if (checkLeap(design, board, player, pos, w, sw, knight)) return true;
  if (checkLeap(design, board, player, pos, e, ne, knight)) return true;
  if (checkLeap(design, board, player, pos, e, se, knight)) return true;
  return false;
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  _.each(board.moves, function(move) {
      if (move.isSimpleMove()) {
          var pos = move.actions[0][0][0];
          var piece = board.getPiece(pos);
          if (piece !== null) {
              if (piece.player == 3) {
                  if (checkDirection && !isAttacked(design, board, pos) && (move.mode == 3)) {
                      move.failed = true;
                      return;
                  }
                  pos = move.actions[0][1][0];
                  if (isAttacked(design, board, pos)) {
                      move.failed = true;
                  }
              } else {
                  var b = board.apply(move);
                  var pos = findKing(design, b);
                  if ((pos !== null) && isAttacked(design, b, pos, b.player)) {
                      move.failed = true;
                  }
              }
          }
      }
  });
  CheckInvariants(board);
}

})();
<commit_msg>Neutral King (checkmate fixed)<commit_after>(function() {

var checkDirection = true;

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name != "neutral-king-invariant") {
      checkVersion(design, name, value);
  }
}

var findKing = function(design, board) {
  var positions = design.allPositions();
  for (var i = 0; i < positions.length; i++) {
       var piece = board.getPiece(positions[i]);
       if ((piece !== null) && (piece.player == 3)) {
           return positions[i];
       }
  }
  return null;
}

var checkDirection = function(design, board, player, pos, dir, leapers, riders) {
  var p = design.navigate(1, pos, dir);
  if (p === null) return false;
  var piece = board.getPiece(p);
  if (piece !== null) {
      if (!_.isUndefined(player)) {
          if (piece.player != player) return false;
      }
      return (_.indexOf(leapers, piece.type) >= 0) || (_.indexOf(riders, piece.type) >= 0);
  }
  while (piece === null) {
      p = design.navigate(1, p, dir);
      if (p === null) return false;
      piece = board.getPiece(p);
  }
  if (!_.isUndefined(player)) {
      if (piece.player != player) return false;
  }
  return _.indexOf(riders, piece.type) >= 0;
}

var checkLeap = function(design, board, player, pos, o, d, knight) {
  var p = design.navigate(1, pos, o);
  if (p === null) return false;
  p = design.navigate(1, p, d);
  if (p === null) return false;
  var piece = board.getPiece(p);
  if (piece === null) return false;
  if (!_.isUndefined(player)) {
      if (piece.player != player) return false;
  }
  return (piece.type == knight);
}

var isAttacked = function(design, board, pos, player) {
  var pawn   = design.getPieceType("Pawn");
  var rook   = design.getPieceType("Rook");
  var knight = design.getPieceType("Knight");
  var bishop = design.getPieceType("Bishop");
  var n  = design.getDirection("n");  var w  = design.getDirection("w");
  var s  = design.getDirection("s");  var e  = design.getDirection("e");
  var nw = design.getDirection("nw"); var sw = design.getDirection("sw");
  var ne = design.getDirection("ne"); var se = design.getDirection("se");
  if (checkDirection(design, board, player, pos, n, [], [rook])) return true;
  if (checkDirection(design, board, player, pos, s,  [], [rook])) return true;
  if (checkDirection(design, board, player, pos, w,  [], [rook])) return true;
  if (checkDirection(design, board, player, pos, e,  [], [rook])) return true;
  if (checkDirection(design, board, player, pos, nw, [pawn], [bishop])) return true;
  if (checkDirection(design, board, player, pos, ne, [pawn], [bishop])) return true;
  if (checkDirection(design, board, player, pos, sw, [], [bishop])) return true;
  if (checkDirection(design, board, player, pos, se, [], [bishop])) return true;
  if (checkLeap(design, board, player, pos, n, nw, knight)) return true;
  if (checkLeap(design, board, player, pos, n, ne, knight)) return true;
  if (checkLeap(design, board, player, pos, s, sw, knight)) return true;
  if (checkLeap(design, board, player, pos, s, se, knight)) return true;
  if (checkLeap(design, board, player, pos, w, nw, knight)) return true;
  if (checkLeap(design, board, player, pos, w, sw, knight)) return true;
  if (checkLeap(design, board, player, pos, e, ne, knight)) return true;
  if (checkLeap(design, board, player, pos, e, se, knight)) return true;
  return false;
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  _.each(board.moves, function(move) {
      if (move.isSimpleMove()) {
          var pos = move.actions[0][0][0];
          var piece = board.getPiece(pos);
          if (piece !== null) {
              var b = board.apply(move);
              if (piece.player == 3) {
                  if (checkDirection && !isAttacked(design, board, pos) && (move.mode == 3)) {
                      move.failed = true;
                      return;
                  }
                  pos = move.actions[0][1][0];
                  if (isAttacked(design, b, pos)) {
                      move.failed = true;
                  }
              } else {
                  var pos = findKing(design, b);
                  if ((pos !== null) && isAttacked(design, b, pos, b.player)) {
                      move.failed = true;
                  }
              }
          }
      }
  });
  CheckInvariants(board);
}

})();
<|endoftext|>


<commit_before>orator.controller('SessionViewController', function ($scope, $state, $stateParams, $http, $upload, $timeout, $interval, player, Sessions) {

	$scope.fileStatus = [];
	$scope.syncComplete = false;

	// empty starting values to make interpolation stop complaining
	$scope.session = {};
	$scope.session.book = {};
	$scope.session.book.bookTracks = [];

	// seeker defaults
	$scope.rocketSeek = {};
	$scope.rocketSeek.hours = null;
	$scope.rocketSeek.minutes = null;
	$scope.rocketSeek.seconds = null;
	$scope.rocketSeek.from = "start";

	// make player available to scope
	$scope.player = player;

	// handle loading/finding of book
	$scope.load = function() {
		var sessionId = $stateParams.sessionId;
		var bookId = $stateParams.bookId;

		// bootstrap from existing configuration
		if(sessionId) {
			var session = Sessions.get({'sessionId': sessionId}, function(){
				$scope.session = session;
				$scope.buildPlaylist();
			});
		} else if(bookId) {
			var session = Sessions.start({'bookId': bookId},
			function() {
				// save book to local scope
				$scope.session = session;
				// update location bar/state with new book id
				$state.go('session', {'sessionId': session.id}, {location: "replace"});
			});
		} else {
			$scope.status.alert = "An error occured while starting the desired session";
		}
	};

	$scope.buildPlaylist = function() {
		var currentTrack = 0;

		// bulid playlist source elements from tracks
		for(var index = 0; index < $scope.session.book.bookTracks.length; index++) {
			var track = $scope.session.book.bookTracks[index];

			// determine current track
			if(track.id == $scope.session.currentTrackId) {
				currentTrack = index;
			}

			track.session = $scope.session;
			player.playlist.add(track);
		}

		// mark first track as active if available
		if(currentTrack < 1 && $scope.session.book.bookTracks &&  $scope.session.book.bookTracks.length > 0) {
			$scope.session.book.bookTracks[0].active = true;
			$scope.session.currentTrackId = $scope.session.book.bookTracks[0].id;
		}
	};

	$scope.getPlayingTrackIndex = function() {
		if(!$scope.syncComplete) {
			return $scope.getSessionTrackIndex();
		}

		if(!player) {
			return 0;
		}

		return player.index();
	};

	// return the index that should be used based on the session value
	$scope.getSessionTrackIndex = function() {
		for(var index = 0; index < $scope.session.book.bookTracks.length; index++) {
			// get track from track list
			var track = $scope.session.book.bookTracks[index];

			// determine current track
			if(track.id == $scope.session.currentTrackId) {
				//console.log('session current track at index: ' + index + ' (with session current track id: ' + $scope.session.currentTrackId + ')');
				return index;
			}
		}
		//console.log('no current track found for id ' + $scope.session.currentTrackId);
		return 0;
	};

	$scope.getPlayingTrack = function() {
		if(!$scope.syncComplete) {
			return $scope.session.book.bookTracks[$scope.getSessionTrackIndex()];
		}
		return player.current();
	};

	// gets the best guess at the current time
	$scope.getCurrentTime = function(hard) {
		var currentTime = 0;

		if(player) {
			currentTime = player.time();
		}

		// hard return means a forced query against the player
		if(hard) {
			return currentTime;
		}

		// if not playing or the session says otherwise, use session time as current time
		if(!$scope.syncComplete && $scope.session) {
			currentTime = $scope.session.secondsOffset;
		}

		// return calculated/best guess time
		return currentTime;
	};

	// update the position in the stored session on the server
	$scope.updatePosition = function(force) {

		// if not yet synced with session don't update
		if(!$scope.syncComplete) {
			return;
		}

		// don't update if no playing is going on
		if(!player || !$scope.session.book.bookTracks || !$scope.session.book.bookTracks.length) {
			//console.log('no audio player, playlist, or playlist length');
			return;
		}

		// get current track
		var currentTrack = $scope.getPlayingTrack();

		// get current time
		var currentTime = $scope.getCurrentTime(true);
		currentTime = Math.floor(currentTime);

		// if the current track and the playing track are the same
		// then check the time.  if it ISN'T then we can skip this part
		// because we've obviously changed tracks.
		if($scope.session.currentTrackId == currentTrack.id) {
			// get session time and check it... if the tracks are the same
			var sessionTime = $scope.session.secondsOffset;
			sessionTime = Math.floor(sessionTime);

			// don't sync if session time is
			// further along than the current time
			// unless it is forced (because of a
			// skip back or something like that)
			if(!force && sessionTime >= currentTime) {
				//console.log('force is: (' + force + ') and session time is ahead of or the same as current play time');
				return;
			}
		} else {
			//console.log('updating with track id: ' + currentTrack.id);
		}

		// only send 0 if forced
		if(currentTime > 0 || force) {

			// fix for null current time (which would only be sent if forced)
			if(!currentTime) {
				currentTime = "0";
			}

			// update session with current time
			var localSession = Sessions.update({sessionId: $scope.session.id, trackId: currentTrack.id}, currentTime, function(){
				// save local session
				$scope.session = localSession;
			}, function() {
				console.log('error while updating session progress');
			});
		}
	};

	// toggle hide/show qr code
	$scope.toggleQR = function() {
		$scope.showQR = !$scope.showQR;
	};

	// toggle hide/show skip
	$scope.toggleSkip = function() {
		$scope.showSkip = !$scope.showSkip;
	};

	// ====================== MEDIA CONTROLS ======================

	$scope.play = function() {
		// don't do anything if already playing
		if(player.playing) {
			return;
		}

		if(!$scope.syncComplete) {
			// get session track index from session
			var trackIndex = $scope.getSessionTrackIndex();

			// seek to
			player.seek($scope.session.secondsOffset);

			//console.log('starting from beginning and wanting to set track : ' + trackIndex + ' at time ' + $scope.session.secondsOffset);

			// start play (at track index);
			player.play(trackIndex);

			// make sync complete
			$scope.syncComplete = true;
		} else {
			// a simple play will suffice
			player.play();
		}

	};

	$scope.pause = function() {
		player.pause();

		// force update after 100ms
		$timeout(function(){
			$scope.updatePosition(true);
		}, 100);
	};

	$scope.nextTrack = function() {
		player.next();
	};

	$scope.previousTrack = function() {
		player.previous();
	};

	$scope.seekForward = function(time) {
		// seek 20 seconds by default
		if(!time) {
			time = 20;
		}
		$scope.seekTo(time, "now");
	};

	$scope.seekBackward = function(time) {
		// seek 20 seconds by default
		if(!time) {
			time = 20;
		}
		if(time > 0) {
			time = time * -1;
		}
		$scope.seekTo(time, "now");
	};

	// pulls seek values from form
	$scope.rocketSeeker = function() {
		// seek
		console.dir($scope.rocketSeek);

		// calculate seconds
		var seek = $scope.rocketSeek;
		var offset = 0;
		if(seek.hours && util.isNumber(seek.hours)) {
			offset += seek.hours * 60 * 60;
		}
		if(seek.minutes && util.isNumber(seek.minutes)) {
			offset += seek.minutes * 60;
		}
		if(seek.seconds && util.isNumber(seek.seconds)) {
			offset += seek.seconds;
		}

		// seek to
		$scope.seekTo(offset, seek.from);

		// reset values (but leave "from" alone);
		$scope.rocketSeek.hours = null;
		$scope.rocketSeek.minutes = null;
		$scope.rocketSeek.seconds = null;
	};

	$scope.seekTo = function(seconds, from, tries, forcePlay, trackIndex) {

		// set defaults
		if(typeof(from) == 'undefined') { from = "start"; }
		if(typeof(tries) == 'undefined') { tries = 1; }
		if(typeof(forcePlay) == 'undefined') { forcePlay = false; }

		// abort when tries is too many
		if(tries < 1) {
			//console.log('remaining tries is less than 1');
			return;
		}

		// start by getting the position it should start at
		var start = 0;
		if("now" == from) {
			start = $scope.getCurrentTime();
		} else if("end" == from) {
			// measure time from the end
			start = $scope.getPlayingTrack().lengthSeconds;

			// end is at...
			console.log("end is at: " + start);

			// time from the end is measured in seconds from the end
			// so make it negative if it isn't already
			if(seconds > 0) {
				seconds = seconds * -1;
			}
		} // "start" is just start = 0 so it's the default case

		// calculate
		var seekTime = (start * 1.0) + (seconds * 1.0);
		seekTime = Math.floor(seekTime);

		// log
		console.log("difference is: " + seconds);
		console.log('want to seek to: ' + seekTime);

		// do seek
		try {
			player.seek(seekTime);
			//console.log('completed seek to: ' + seekTime + ' from: ' + from);

			if(forcePlay && player.playing) {
				// force play on proper track
				if(typeof(trackIndex) != 'undefined') {
					//console.log('playing specified track index');
					player.play(trackIndex);
				} else {
					//console.log('undefined track index given');
					player.play();
				}
			}
			// update session time
			$scope.session.secondsOffset = seconds;

			// mark sync as complete if the time is changed in any case
			$scope.syncComplete = true;
		} catch (e) {
			// log
			//console.log('there was an error attempting to seek, try again... (' + e + ')');

			// seek again, but 50ms later
			$timeout(function(){
				$scope.seekTo(seconds, from, --tries, forcePlay, trackIndex);
			}, 50);
			// stop doing, and return
			return;
		}

		// force update after 100ms
		$timeout(function(){
			$scope.updatePosition(true);
		}, 100);
	};

	// ====================== INIT ======================

	// load
	$scope.load();

	// set up position to update while track is playing
	$interval(function(){
		$scope.updatePosition();
	}, 5000); // every 5 seconds

});<commit_msg>fixed bug with seek from control<commit_after>orator.controller('SessionViewController', function ($scope, $state, $stateParams, $http, $upload, $timeout, $interval, player, Sessions) {

	$scope.fileStatus = [];
	$scope.syncComplete = false;

	// empty starting values to make interpolation stop complaining
	$scope.session = {};
	$scope.session.book = {};
	$scope.session.book.bookTracks = [];

	// seeker defaults
	$scope.rocketSeek = {};
	$scope.rocketSeek.hours = null;
	$scope.rocketSeek.minutes = null;
	$scope.rocketSeek.seconds = null;
	$scope.rocketSeek.from = "start";

	// make player available to scope
	$scope.player = player;

	// handle loading/finding of book
	$scope.load = function() {
		var sessionId = $stateParams.sessionId;
		var bookId = $stateParams.bookId;

		// bootstrap from existing configuration
		if(sessionId) {
			var session = Sessions.get({'sessionId': sessionId}, function(){
				$scope.session = session;
				$scope.buildPlaylist();
			});
		} else if(bookId) {
			var session = Sessions.start({'bookId': bookId},
			function() {
				// save book to local scope
				$scope.session = session;
				// update location bar/state with new book id
				$state.go('session', {'sessionId': session.id}, {location: "replace"});
			});
		} else {
			$scope.status.alert = "An error occured while starting the desired session";
		}
	};

	$scope.buildPlaylist = function() {
		var currentTrack = 0;

		// bulid playlist source elements from tracks
		for(var index = 0; index < $scope.session.book.bookTracks.length; index++) {
			var track = $scope.session.book.bookTracks[index];

			// determine current track
			if(track.id == $scope.session.currentTrackId) {
				currentTrack = index;
			}

			track.session = $scope.session;
			player.playlist.add(track);
		}

		// mark first track as active if available
		if(currentTrack < 1 && $scope.session.book.bookTracks &&  $scope.session.book.bookTracks.length > 0) {
			$scope.session.book.bookTracks[0].active = true;
			$scope.session.currentTrackId = $scope.session.book.bookTracks[0].id;
		}
	};

	$scope.getPlayingTrackIndex = function() {
		if(!$scope.syncComplete) {
			return $scope.getSessionTrackIndex();
		}

		if(!player) {
			return 0;
		}

		return player.index();
	};

	// return the index that should be used based on the session value
	$scope.getSessionTrackIndex = function() {
		for(var index = 0; index < $scope.session.book.bookTracks.length; index++) {
			// get track from track list
			var track = $scope.session.book.bookTracks[index];

			// determine current track
			if(track.id == $scope.session.currentTrackId) {
				//console.log('session current track at index: ' + index + ' (with session current track id: ' + $scope.session.currentTrackId + ')');
				return index;
			}
		}
		//console.log('no current track found for id ' + $scope.session.currentTrackId);
		return 0;
	};

	$scope.getPlayingTrack = function() {
		if(!$scope.syncComplete) {
			return $scope.session.book.bookTracks[$scope.getSessionTrackIndex()];
		}
		return player.current();
	};

	// gets the best guess at the current time
	$scope.getCurrentTime = function(hard) {
		var currentTime = 0;

		if(player) {
			currentTime = player.time();
		}

		// hard return means a forced query against the player
		if(hard) {
			return currentTime;
		}

		// if not playing or the session says otherwise, use session time as current time
		if(!$scope.syncComplete && $scope.session) {
			currentTime = $scope.session.secondsOffset;
		}

		// return calculated/best guess time
		return currentTime;
	};

	// update the position in the stored session on the server
	$scope.updatePosition = function(force) {

		// if not yet synced with session don't update
		if(!$scope.syncComplete) {
			return;
		}

		// don't update if no playing is going on
		if(!player || !$scope.session.book.bookTracks || !$scope.session.book.bookTracks.length) {
			//console.log('no audio player, playlist, or playlist length');
			return;
		}

		// get current track
		var currentTrack = $scope.getPlayingTrack();

		// get current time
		var currentTime = $scope.getCurrentTime(true);
		currentTime = Math.floor(currentTime);

		// if the current track and the playing track are the same
		// then check the time.  if it ISN'T then we can skip this part
		// because we've obviously changed tracks.
		if($scope.session.currentTrackId == currentTrack.id) {
			// get session time and check it... if the tracks are the same
			var sessionTime = $scope.session.secondsOffset;
			sessionTime = Math.floor(sessionTime);

			// don't sync if session time is
			// further along than the current time
			// unless it is forced (because of a
			// skip back or something like that)
			if(!force && sessionTime >= currentTime) {
				//console.log('force is: (' + force + ') and session time is ahead of or the same as current play time');
				return;
			}
		} else {
			//console.log('updating with track id: ' + currentTrack.id);
		}

		// only send 0 if forced
		if(currentTime > 0 || force) {

			// fix for null current time (which would only be sent if forced)
			if(!currentTime) {
				currentTime = "0";
			}

			// update session with current time
			var localSession = Sessions.update({sessionId: $scope.session.id, trackId: currentTrack.id}, currentTime, function(){
				// save local session
				$scope.session = localSession;
			}, function() {
				console.log('error while updating session progress');
			});
		}
	};

	// toggle hide/show qr code
	$scope.toggleQR = function() {
		$scope.showQR = !$scope.showQR;
	};

	// toggle hide/show skip
	$scope.toggleSkip = function() {
		$scope.showSkip = !$scope.showSkip;
	};

	// ====================== MEDIA CONTROLS ======================

	$scope.play = function() {
		// don't do anything if already playing
		if(player.playing) {
			return;
		}

		if(!$scope.syncComplete) {
			// get session track index from session
			var trackIndex = $scope.getSessionTrackIndex();

			// seek to
			player.seek($scope.session.secondsOffset);

			//console.log('starting from beginning and wanting to set track : ' + trackIndex + ' at time ' + $scope.session.secondsOffset);

			// start play (at track index);
			player.play(trackIndex);

			// make sync complete
			$scope.syncComplete = true;
		} else {
			// a simple play will suffice
			player.play();
		}

	};

	$scope.pause = function() {
		player.pause();

		// force update after 100ms
		$timeout(function(){
			$scope.updatePosition(true);
		}, 100);
	};

	$scope.nextTrack = function() {
		player.next();
	};

	$scope.previousTrack = function() {
		player.previous();
	};

	$scope.seekForward = function(time) {
		// seek 20 seconds by default
		if(!time) {
			time = 20;
		}
		$scope.seekTo(time, "now");
	};

	$scope.seekBackward = function(time) {
		// seek 20 seconds by default
		if(!time) {
			time = 20;
		}
		if(time > 0) {
			time = time * -1;
		}
		$scope.seekTo(time, "now");
	};

	// pulls seek values from form
	$scope.rocketSeeker = function() {
		// calculate seconds
		var seek = $scope.rocketSeek;
		var offset = 0;
		if(seek.hours && util.isNumber(seek.hours)) {
			offset += seek.hours * 60 * 60;
		}
		if(seek.minutes && util.isNumber(seek.minutes)) {
			offset += seek.minutes * 60;
		}
		if(seek.seconds && util.isNumber(seek.seconds)) {
			offset += seek.seconds;
		}

		// seek to
		$scope.seekTo(offset, seek.from);

		// reset values (but leave "from" alone)
		$scope.rocketSeek.hours = null;
		$scope.rocketSeek.minutes = null;
		$scope.rocketSeek.seconds = null;
		$scope.rocketSeek.from = seek.from;
	};

	$scope.seekTo = function(seconds, from, tries, forcePlay, trackIndex) {

		// set defaults
		if(typeof(from) == 'undefined') { from = "start"; }
		if(typeof(tries) == 'undefined') { tries = 1; }
		if(typeof(forcePlay) == 'undefined') { forcePlay = false; }

		// abort when tries is too many
		if(tries < 1) {
			//console.log('remaining tries is less than 1');
			return;
		}

		// start by getting the position it should start at
		var start = 0;
		if("now" == from) {
			start = $scope.getCurrentTime();
		} else if("end" == from) {
			// measure time from the end
			start = $scope.getPlayingTrack().lengthSeconds;

			// end is at...
			//console.log("end is at: " + start);

			// time from the end is measured in seconds from the end
			// so make it negative if it isn't already
			if(seconds > 0) {
				seconds = seconds * -1;
			}
		} // "start" is just start = 0 so it's the default case

		// calculate
		var seekTime = (start * 1.0) + (seconds * 1.0);
		seekTime = Math.floor(seekTime);

		// log
		//console.log("difference is: " + seconds);
		//console.log('want to seek to: ' + seekTime);

		// do seek
		try {
			player.seek(seekTime);
			//console.log('completed seek to: ' + seekTime + ' from: ' + from);

			if(forcePlay && player.playing) {
				// force play on proper track
				if(typeof(trackIndex) != 'undefined') {
					//console.log('playing specified track index');
					player.play(trackIndex);
				} else {
					//console.log('undefined track index given');
					player.play();
				}
			}
			// update session time
			$scope.session.secondsOffset = seconds;

			// mark sync as complete if the time is changed in any case
			$scope.syncComplete = true;
		} catch (e) {
			// log
			//console.log('there was an error attempting to seek, try again... (' + e + ')');

			// seek again, but 50ms later
			$timeout(function(){
				$scope.seekTo(seconds, from, --tries, forcePlay, trackIndex);
			}, 50);
			// stop doing, and return
			return;
		}

		// force update after 100ms
		$timeout(function(){
			$scope.updatePosition(true);
		}, 100);
	};

	// ====================== INIT ======================

	// load
	$scope.load();

	// set up position to update while track is playing
	$interval(function(){
		$scope.updatePosition();
	}, 5000); // every 5 seconds

});<|endoftext|>


<commit_before>const getFields = (obj, fields, defaultValue) => {
  const result = { };
  for(const field of fields) result[field] = getField(obj, field, defaultValue);
  return result;
}
<commit_msg>Add Jdoc style comment<commit_after>/**
  * Creates a new object whose fields are either equal to obj[field] or defaultValue.
  * Useful for extracting a subset of fields from an object.
  *
  * @param obj the object to query the fields for.
  * @param fields an array of the target fields, in string form.
  * @param defaultValue
 */
const getFields = (obj, fields, defaultValue) => {
  const result = { };
  for(const field of fields) result[field] = getField(obj, field, defaultValue);
  return result;
}
<|endoftext|>


<commit_before>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

describe('reportService', function() {

    var $rootScope, $httpBackend, reportService,
        report, report2, paramOptions,
        REPORT_ID = '574425e4-0288-11e7-bcbe-3417eb83144e',
        REPORT_ID2 = '84a6b8a2-0289-11e7-a4b0-3417eb83144e',
        REQUISITIONS = 'requisitions',
        SELECT_EXPRESSION = '/api/periods';

    beforeEach(function() {
        module('report', function() {
            // no additional mocks required
        });

        inject(function(_$httpBackend_, _$rootScope_, _reportService_) {
            $httpBackend = _$httpBackend_;
            $rootScope = _$rootScope_;
            reportService = _reportService_;
        });

        report = { id: REPORT_ID };
        report2 = { id: REPORT_ID2 };
        paramOptions = [{name: 'name1', value: 'value1'}, {name: 'name2', value: 'value2'}];
    });

    describe('getReport()', function() {

        beforeEach(function() {
            $httpBackend.when('GET', '/api/reports/templates/requisitions/' + REPORT_ID)
                .respond(200, report);
        });

        it('should return promise', function() {
            var promise = reportService.getReport(REQUISITIONS, REPORT_ID);
            $httpBackend.flush();

            expect(angular.isFunction(promise.then)).toBe(true);
        });

        it('should retrieve single report', function() {
            var data, promise;

            promise = reportService.getReport(REQUISITIONS, REPORT_ID);
            promise.then(function(response) {
                data = response;
            });
            $httpBackend.flush();
            $rootScope.$apply();

            expect(data).not.toBe(undefined);
            expect(data.id).toBe(REPORT_ID);
        });
    });

    describe('getReports()', function() {

        beforeEach(function() {
           $httpBackend.when('GET', '/api/reports/templates/requisitions')
                .respond(200, [report, report2]);
        });

        it('should return promise', function() {
            var promise = reportService.getReports(REQUISITIONS);
            $httpBackend.flush();

            expect(angular.isFunction(promise.then)).toBe(true);
        });

        it('should retrieve all reports for a module', function() {
            var data, promise;

            promise = reportService.getReports(REQUISITIONS);
            promise.then(function(response) {
                data = response;
            });
            $httpBackend.flush();
            $rootScope.$apply();

            expect(data).not.toBe(undefined);
            expect(data.length).toEqual(2);
            expect(data[0].id).toBe(REPORT_ID);
            expect(data[1].id).toBe(REPORT_ID2);
        });
    });

    describe('getReportParamsOptions()', function() {

        beforeEach(function() {
            $httpBackend.when('GET', SELECT_EXPRESSION).respond(200, paramOptions);
        });

        it('should return promise', function() {
            var promise = reportService.getReportParamsOptions(SELECT_EXPRESSION);
            $httpBackend.flush();

            expect(angular.isFunction(promise.then)).toBe(true);
        });

        it('should retrieve param options through select expression', function() {
            var data, promise;

            promise = reportService.getReportParamsOptions(SELECT_EXPRESSION);
            promise.then(function(response) {
                data = response.data;
            });
            $httpBackend.flush();
            $rootScope.$apply();

            expect(data).not.toBe(undefined);
            expect(data).toEqual(paramOptions);
        });
    });

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });
});
<commit_msg>OLMIS-2006: Mock url factory during test<commit_after>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

describe('reportService', function() {

    var $rootScope, $httpBackend, reportService, urlFactoryMock,
        report, report2, paramOptions,
        REPORT_ID = '574425e4-0288-11e7-bcbe-3417eb83144e',
        REPORT_ID2 = '84a6b8a2-0289-11e7-a4b0-3417eb83144e',
        REQUISITIONS = 'requisitions',
        SELECT_EXPRESSION = '/api/periods';

    beforeEach(function($provide) {
        module('report', function($provide) {
            urlFactoryMock = jasmine.createSpy();
            $provide.factory('openlmisUrlFactory', function() {
                return urlFactoryMock;
            });
            urlFactoryMock.andCallFake(function(parameter) {
                return parameter;
            });
        });

        inject(function(_$httpBackend_, _$rootScope_, _reportService_) {
            $httpBackend = _$httpBackend_;
            $rootScope = _$rootScope_;
            reportService = _reportService_;
        });

        report = { id: REPORT_ID };
        report2 = { id: REPORT_ID2 };
        paramOptions = [{name: 'name1', value: 'value1'}, {name: 'name2', value: 'value2'}];
    });

    describe('getReport()', function() {

        beforeEach(function() {
            $httpBackend.when('GET', '/api/reports/templates/requisitions/' + REPORT_ID)
                .respond(200, report);
        });

        it('should return promise', function() {
            var promise = reportService.getReport(REQUISITIONS, REPORT_ID);
            $httpBackend.flush();

            expect(angular.isFunction(promise.then)).toBe(true);
        });

        it('should retrieve single report', function() {
            var data, promise;

            promise = reportService.getReport(REQUISITIONS, REPORT_ID);
            promise.then(function(response) {
                data = response;
            });
            $httpBackend.flush();
            $rootScope.$apply();

            expect(data).not.toBe(undefined);
            expect(data.id).toBe(REPORT_ID);
        });
    });

    describe('getReports()', function() {

        beforeEach(function() {
           $httpBackend.when('GET', '/api/reports/templates/requisitions')
                .respond(200, [report, report2]);
        });

        it('should return promise', function() {
            var promise = reportService.getReports(REQUISITIONS);
            $httpBackend.flush();

            expect(angular.isFunction(promise.then)).toBe(true);
        });

        it('should retrieve all reports for a module', function() {
            var data, promise;

            promise = reportService.getReports(REQUISITIONS);
            promise.then(function(response) {
                data = response;
            });
            $httpBackend.flush();
            $rootScope.$apply();

            expect(data).not.toBe(undefined);
            expect(data.length).toEqual(2);
            expect(data[0].id).toBe(REPORT_ID);
            expect(data[1].id).toBe(REPORT_ID2);
        });
    });

    describe('getReportParamsOptions()', function() {

        beforeEach(function() {
            $httpBackend.when('GET', SELECT_EXPRESSION).respond(200, paramOptions);
        });

        it('should return promise', function() {
            var promise = reportService.getReportParamsOptions(SELECT_EXPRESSION);
            $httpBackend.flush();

            expect(angular.isFunction(promise.then)).toBe(true);
        });

        it('should retrieve param options through select expression', function() {
            var data, promise;

            promise = reportService.getReportParamsOptions(SELECT_EXPRESSION);
            promise.then(function(response) {
                data = response.data;
            });
            $httpBackend.flush();
            $rootScope.$apply();

            expect(data).not.toBe(undefined);
            expect(data).toEqual(paramOptions);
        });
    });

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });
});
<|endoftext|>


<commit_before>YUI.add('wegas-menu', function (Y) {
    'use strict';

    var WegasMenu,
    CONTENT_BOX="contentBox";

    WegasMenu = Y.Base.create("wegas-menu", Y.Widget, [Y.WidgetChild], {
        BOUNDING_TEMPLATE: "

",
        CONTENT_TEMPLATE:"

",
        nodeInstances: null,
        eventInstances: null,
        clickHandler: null,

        initializer: function () {
            this.nodeInstances = [];
            this.eventInstances = [];
            this.publish("itemClick", {
                emitFacade: true,
                bubbles: true
            });
        },
        renderUI: function () {
            var listItem, item;
            for (var i in this.get("items")){
                item = this.get("items")[i];
                listItem = this.itemCreator(item);
                this.get(CONTENT_BOX).append(listItem);
                this.nodeInstances.push(listItem);
            }
        },
        bindUI: function () {
            this.clickHandler = this.get(CONTENT_BOX).delegate('click', function(e) {					// Listen for click events on the table
                e.stopImmediatePropagation();
                this.fire("itemClick", {
                    parent: this.get("parent"),
                    item:  e.currentTarget.nodeName,
                    params: this.get('params')
                });
            }, 'li', this);
        },

        destructor: function () {
            this.clickHandler.detach();
            for(var n in this.nodeInstances){
                this.nodeInstances[n].destroy();
            }
        },
        itemCreator: function (item) {
            var node;
            if(item.imgSrc){
                node = Y.Node.create("

");
            } else {
                node = Y.Node.create("
" + item.label + "
");
            }
            node.nodeName = item.label
            node.addClass(this.getClassName("itemlist", this.get("horizontal") ? "horizontal" : "vertical"));
            return node
        }
    },{
        NAME:"wegas-menu",
        CSS_PREFIX: "wegas-menu",
        ATTRS:{
            horizontal: {
                value: false,
                validator: Y.Lang.isBoolean
            },
            items:{
                validator: function(o){
                    var valid = Y.Lang.isArray(o) || o === null;
                    for(var i in o){
                        valid = Y.Lang.isString(o[i].label) && (Y.Lang.isString(o[i].imgSrc) || o[i].imgSrc == null)
                    }
                    return valid;
                }
            },
            title: {
                value: null,
                validator: Y.Lang.isString
            },
            params:{                                                            // Given input params returned with the click event, a reference for instance
                value: null
            }
        }
    });

    Y.namespace('Wegas').WegasMenu = WegasMenu;
});<commit_msg>refactor for further improvements (soon)<commit_after>YUI.add('wegas-menu', function (Y) {
    'use strict';

    var WegasMenu,
    CONTENT_BOX="contentBox";

    WegasMenu = Y.Base.create("wegas-menu", Y.Widget, [Y.WidgetChild], {
        BOUNDING_TEMPLATE: "

",
        CONTENT_TEMPLATE:"

",
        nodeInstances: null,
        eventInstances: null,
        clickHandler: null,

        initializer: function () {
            this.nodeInstances = [];
            this.eventInstances = [];
            this.publish("itemClick", {
                emitFacade: true,
                bubbles: true
            });
        },
        renderUI: function () {
            var listItem, item;
            for (var i in this.get("items")){
                item = this.get("items")[i];
                listItem = this.itemCreator(item);
                this.get(CONTENT_BOX).append(listItem);
                this.nodeInstances.push(listItem);
            }
        },
        bindUI: function () {
            this.clickHandler = this.get(CONTENT_BOX).delegate('click', function(e) {					// Listen for click events on the table
                e.stopImmediatePropagation();
                this.fire("itemClick", {
                    parent: this.get("parent"),
                    item:  e.currentTarget.nodeName,
                    params: this.get('params')
                });
            }, 'li', this);
        },

        destructor: function () {
            this.clickHandler.detach();
            for(var n in this.nodeInstances){
                this.nodeInstances[n].destroy();
            }
        },
        itemCreator: function (item) {
            var node;
            if(item.imgSrc){
                node = Y.Node.create("

");
            } else {
                node = Y.Node.create("
" + item.label + "
");
            }
            node.nodeName = item.label
            node.addClass(this.getClassName("itemlist", this.get("horizontal") ? "horizontal" : "vertical"));
            return node
        }
    },{
        NAME:"wegas-menu",
        CSS_PREFIX: "wegas-menu",
        ATTRS:{
            horizontal: {
                value: false,
                validator: Y.Lang.isBoolean
            },
            items:{
                validator: function(o){
                    var valid = Y.Lang.isArray(o) || o === null;
                    for(var i in o){
                        valid = Y.Lang.isString(o[i].label) && (Y.Lang.isString(o[i].imgSrc) || o[i].imgSrc == null)
                    }
                    return valid;
                }
            },
            mainItem: {
                value: null,
                validator: Y.Lang.isString
            },
            params:{                                                            // Given input params returned with the click event, a reference for instance
                value: null
            }
        }
    });

    Y.namespace('Wegas').WegasMenu = WegasMenu;
});<|endoftext|>


<commit_before>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright (c) 2006  Center for History and New Media
                        George Mason University, Fairfax, Virginia, USA
                        http://chnm.gmu.edu

    Licensed under the Educational Community License, Version 1.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.opensource.org/licenses/ecl1.php

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    ***** END LICENSE BLOCK *****
*/

Zotero.Creator = function (creatorID) {
	this._creatorID = creatorID ? creatorID : null;
	this._init();
}

Zotero.Creator.prototype._init = function () {
	this._firstName = null;
	this._lastName = null;
	this._fieldMode = null;
	this._birthYear = null;
	this._key = null;
	this._dateModified = null;

	this._creatorDataID = null;
	this._loaded = false;
	this._changed = false;
	this._previousData = false;
}

Zotero.Creator.prototype.__defineGetter__('id', function () { return this._creatorID; });
Zotero.Creator.prototype.__defineGetter__('creatorDataID', function () { return this._get('creatorDataID'); });

Zotero.Creator.prototype.__defineSetter__('creatorID', function (val) { this._set('creatorID', val); });
Zotero.Creator.prototype.__defineGetter__('firstName', function () { return this._get('firstName'); });
Zotero.Creator.prototype.__defineSetter__('firstName', function (val) { this._set('firstName', val); });
Zotero.Creator.prototype.__defineGetter__('lastName', function () { return this._get('lastName'); });
Zotero.Creator.prototype.__defineSetter__('lastName', function (val) { this._set('lastName', val); });
Zotero.Creator.prototype.__defineGetter__('fieldMode', function () { return this._get('fieldMode'); });
Zotero.Creator.prototype.__defineSetter__('fieldMode', function (val) { this._set('fieldMode', val); });
Zotero.Creator.prototype.__defineGetter__('birthYear', function () { return this._get('birthYear'); });
Zotero.Creator.prototype.__defineSetter__('birthYear', function (val) { this._set('birthYear', val); });
Zotero.Creator.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
Zotero.Creator.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
Zotero.Creator.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Creator.prototype.__defineSetter__('key', function (val) { this._set('key', val); });

// Block properties that can't be set this way
Zotero.Creator.prototype.__defineSetter__('id', function () { this._set('id', val); });
Zotero.Creator.prototype.__defineSetter__('creatorDataID', function () { this._set('creatorDataID', val); });

Zotero.Creator.prototype._get = function (field) {
	if (this.id && !this._loaded) {
		this.load();
	}
	return this['_' + field];
}

Zotero.Creator.prototype._set = function (field, val) {
	switch (field) {
		case 'id': // set using constructor
		//case 'creatorID': // set using constructor
		case 'creatorDataID':
			throw ("Invalid field '" + field + "' in Zotero.Creator.set()");
	}

	if (this.id) {
		if (!this._loaded) {
			this.load();
		}
	}
	else {
		this._loaded = true;
	}

	this._checkValue(field, val);

	if (this['_' + field] != val) {
		if (!this._changed) {
			this._changed = {};
		}
		this._changed[field] = true;
		if (this.id && this.exists() && !this._previousData) {
			this._previousData = this.serialize();
		}

		this['_' + field] = val;
	}
}

Zotero.Creator.prototype.setFields = function(fields) {
	for (var field in fields) {
		this[field] = fields[field];
	}
}

/**
 * Check if creator exists in the database
 *
 * @return	bool			TRUE if the creator exists, FALSE if not
 */
Zotero.Creator.prototype.exists = function() {
	if (!this.id) {
		throw ('creatorID not set in Zotero.Creator.exists()');
	}

	var sql = "SELECT COUNT(*) FROM creators WHERE creatorID=?";
	return !!Zotero.DB.valueQuery(sql, this.id);
}

Zotero.Creator.prototype.hasChanged = function () {
	return this._changed;
}

Zotero.Creator.prototype.save = function () {
	if (!this.firstName && !this.lastName) {
		throw ('First and last name are empty in Zotero.Creator.save()');
	}

	if (!this.hasChanged()) {
		Zotero.debug("Creator " + this.id + " has not changed");
		return false;
	}

	if (this.fieldMode == 1 && this.firstName) {
		throw ("First name ('" + this.firstName + "') must be empty in single-field mode in Zotero.Creator.save()");
	}

	Zotero.DB.beginTransaction();

	// ID change
	if (this._changed['creatorID']) {
		var oldID = this._previousData.primary.creatorID;
		var params = [this.id, oldID];

		Zotero.debug("Changing creatorID " + oldID + " to " + this.id);

		var row = Zotero.DB.rowQuery("SELECT * FROM creators WHERE creatorID=?", oldID);
		// Add a new row so we can update the old rows despite FK checks
		// Use temp key due to UNIQUE constraint on key column
		Zotero.DB.query("INSERT INTO creators VALUES (?, ?, ?, ?)",
			[this.id, row.creatorDataID, row.dateModified, 'TEMPKEY']);

		Zotero.DB.query("UPDATE itemCreators SET creatorID=? WHERE creatorID=?", params);

		Zotero.DB.query("DELETE FROM creators WHERE creatorID=?", oldID);
		Zotero.DB.query("UPDATE creators SET key=? WHERE creatorID=?", [row.key, this.id]);

		Zotero.Creators.unload(oldID);
		Zotero.Notifier.trigger('id-change', 'creator', oldID + '-' + this.id);

		// update caches
	}

	var isNew = !this.id || !this.exists();

	try {
		// how to know if date modified changed (in server code too?)

		var creatorID = this.id ? this.id : Zotero.ID.get('creators');

		Zotero.debug("Saving creator " + this.id);

		var key = this.key ? this.key : this._generateKey();

		// If this was the only creator with the previous data,
		// see if we can reuse or remove the old data row
		if (this.creatorDataID) {
			var count = Zotero.Creators.countCreatorsWithData(this.creatorDataID);
			if (count == 1) {
				var newCreatorDataID = Zotero.Creators.getDataID(this);
				// Data hasn't changed
				if (this.creatorDataID == newCreatorDataID) {
					var creatorDataID = this.creatorDataID;
				}
				// Existing data row with the new data -- switch to that
				// and delete old row
				else if (newCreatorDataID) {
					var creatorDataID = newCreatorDataID;
					Zotero.Creators.deleteData(this.creatorDataID);
				}
				// Update current data row with new data
				else {
					Zotero.Creators.updateData(this.creatorDataID, this);
					var creatorDataID = this.creatorDataID;
				}
			}
		}

		if (!creatorDataID) {
			var creatorDataID = Zotero.Creators.getDataID(this, true);
		}

		var columns = ['creatorID', 'creatorDataID', 'dateModified', 'key'];
		var placeholders = ['?', '?', '?', '?'];
		var sqlValues = [
			creatorID ? { int: creatorID } : null,
			{ int: creatorDataID },
			// If date modified hasn't changed, use current timestamp
			this._changed.dateModified ?
				this.dateModified : Zotero.DB.transactionDateTime,
			key
		];

		var sql = "REPLACE INTO creators (" + columns.join(', ') + ") VALUES ("
			+ placeholders.join(', ') + ")";
		var insertID = Zotero.DB.query(sql, sqlValues);
		if (!creatorID) {
			creatorID = insertID;
		}

		this.updateLinkedItems(creatorID);

		Zotero.DB.commitTransaction();
	}
	catch (e) {
		Zotero.DB.rollbackTransaction();
		throw (e);
	}

	// If successful, set values in object
	if (!this.id) {
		this._creatorID = creatorID;
	}
	if (!this.key) {
		this._key = key;
	}
	if (!this.creatorDataID) {
		this._creatorDataID = creatorDataID;
	}

	Zotero.Creators.reload(this.id);

	if (isNew) {
		Zotero.Notifier.trigger('add', 'creator', this.id);
	}
	else {
		Zotero.Notifier.trigger('modify', 'creator', this.id, this._previousData);
	}

	return this.id;
}

Zotero.Creator.prototype.countLinkedItems = function() {
	var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?";
	return Zotero.DB.valueQuery(sql, this.id);
}

Zotero.Creator.prototype.getLinkedItems = function () {
	var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
	return Zotero.DB.columnQuery(sql, this.id);
}

Zotero.Creator.prototype.updateLinkedItems = function () {
	Zotero.DB.beginTransaction();

	var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
	var changedItemIDs = Zotero.DB.columnQuery(sql, this.id);

	if (!changedItemIDs) {
		Zotero.DB.commitTransaction();
		return;
	}

	var notifierData = {};
	for each(var id in changedItemIDs) {
		var item = Zotero.Items.get(id);
		if (item) {
			notifierData[item.id] = { old: item.serialize() };
		}
	}

	sql = "UPDATE items SET dateModified=? WHERE itemID IN "
		+ "(SELECT itemID FROM itemCreators WHERE creatorID=?)";
	Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id]);

	Zotero.Items.reload(changedItemIDs);

	Zotero.DB.commitTransaction();

	Zotero.Notifier.trigger('modify', 'item', changedItemIDs, notifierData);
}

Zotero.Creator.prototype.equals = function (creator) {
	return (creator.firstName == this.firstName) &&
		(creator.lastName == this.lastName) &&
		(creator.fieldMode == this.fieldMode) &&
		(creator.shortName == this.shortName) &&
		(creator.birthYear == this.birthYear);
}

Zotero.Creator.prototype.serialize = function () {
	var obj = {};

	obj.primary = {};
	obj.primary.creatorID = this.id;
	obj.primary.dateModified = this.dateModified;
	obj.primary.key = this.key;

	obj.fields = {};
	if (this.fieldMode == 1) {
		obj.fields.name = this.lastName;
	}
	else {
		obj.fields.firstName = this.firstName;
		obj.fields.lastName = this.lastName;
	}
	obj.fields.fieldMode = this.fieldMode;
	obj.fields.shortName = this.shortName;
	obj.fields.birthYear = this.birthYear;

	return obj;
}

/**
 * Remove creator from all linked items
 *
 * Creators.erase() should be used instead of this
 *
 * Actual deletion of creator occurs in Zotero.Creators.purge(),
 * which is called by Creators.erase()
 */
Zotero.Creator.prototype.erase = function () {
	if (!this.id) {
		return false;
	}

	Zotero.debug("Deleting creator " + this.id);

	// TODO: notifier
	var changedItems = [];
	var changedItemsNotifierData = {};

	Zotero.DB.beginTransaction();

	var toSave = {};

	var linkedItemIDs = this.getLinkedItems();
	for each(var itemID in linkedItemIDs) {
		var item = Zotero.Items.get(itemID)
		if (!item) {
			throw ('Linked item not found in Zotero.Creator.erase()');
		}

		var pos = item.getCreatorPosition(this.id);
		if (!pos) {
			throw ('Creator not found in linked item in Zotero.Creator.erase()');
		}

		item.removeCreator(pos);

		if (!toSave[item.id]) {
			toSave[item.id] = item;
		}
	}

	for each(var item in toSave) {
		item.save();
	}

	Zotero.DB.commitTransaction();
}

// Also called from Zotero.Creators.reload()
Zotero.Creator.prototype.load = function () {
	Zotero.debug("Loading data for creator " + this.id + " in Zotero.Creator.load()");

	if (!this.id) {
		throw ("creatorID not set in Zotero.Creator.load()");
	}

	var sql = "SELECT key, dateModified, creatorDataID, CD.* "
			+ "FROM creators C NATURAL JOIN creatorData CD WHERE creatorID=?";
	var data = Zotero.DB.rowQuery(sql, this.id);

	this._init();
	this._loaded = true;

	if (!data) {
		return;
	}

	for (var key in data) {
		this['_' + key] = data[key];
	}
}

Zotero.Creator.prototype._checkValue = function (field, value) {
	if (this['_' + field] === undefined) {
		throw ("Invalid property " + field + " in Zotero.Creator._checkValue()");
	}

	// Data validation
	switch (field) {
		case 'fieldMode':
			if (value !== 0 && value !== 1) {
				this._invalidValueError(field, value);
			}
			break;

		case 'key':
			var re = /^[23456789ABCDEFGHIJKMNPQRSTUVWXTZ]{8}$/
			if (!re.test(value)) {
				this._invalidValueError(field, value);
			}
			break;

		case 'dateModified':
			if (value !== '' && !Zotero.Date.isSQLDateTime(value)) {
				this._invalidValueError(field, value);
			}
			break;
	}
}

Zotero.Creator.prototype._generateKey = function () {
	return Zotero.ID.getKey();
}

Zotero.Creator.prototype._invalidValueError = function (field, value) {
	throw ("Invalid '" + field + "' value '" + value + "' in Zotero.Creator._invalidValueError()");
}
<commit_msg>Fix error saving new creators after r3178<commit_after>/*
    ***** BEGIN LICENSE BLOCK *****

    Copyright (c) 2006  Center for History and New Media
                        George Mason University, Fairfax, Virginia, USA
                        http://chnm.gmu.edu

    Licensed under the Educational Community License, Version 1.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.opensource.org/licenses/ecl1.php

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    ***** END LICENSE BLOCK *****
*/

Zotero.Creator = function (creatorID) {
	this._creatorID = creatorID ? creatorID : null;
	this._init();
}

Zotero.Creator.prototype._init = function () {
	this._firstName = null;
	this._lastName = null;
	this._fieldMode = null;
	this._birthYear = null;
	this._key = null;
	this._dateModified = null;

	this._creatorDataID = null;
	this._loaded = false;
	this._changed = false;
	this._previousData = false;
}

Zotero.Creator.prototype.__defineGetter__('id', function () { return this._creatorID; });
Zotero.Creator.prototype.__defineGetter__('creatorDataID', function () { return this._get('creatorDataID'); });

Zotero.Creator.prototype.__defineSetter__('creatorID', function (val) { this._set('creatorID', val); });
Zotero.Creator.prototype.__defineGetter__('firstName', function () { return this._get('firstName'); });
Zotero.Creator.prototype.__defineSetter__('firstName', function (val) { this._set('firstName', val); });
Zotero.Creator.prototype.__defineGetter__('lastName', function () { return this._get('lastName'); });
Zotero.Creator.prototype.__defineSetter__('lastName', function (val) { this._set('lastName', val); });
Zotero.Creator.prototype.__defineGetter__('fieldMode', function () { return this._get('fieldMode'); });
Zotero.Creator.prototype.__defineSetter__('fieldMode', function (val) { this._set('fieldMode', val); });
Zotero.Creator.prototype.__defineGetter__('birthYear', function () { return this._get('birthYear'); });
Zotero.Creator.prototype.__defineSetter__('birthYear', function (val) { this._set('birthYear', val); });
Zotero.Creator.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
Zotero.Creator.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
Zotero.Creator.prototype.__defineGetter__('key', function () { return this._get('key'); });
Zotero.Creator.prototype.__defineSetter__('key', function (val) { this._set('key', val); });

// Block properties that can't be set this way
Zotero.Creator.prototype.__defineSetter__('id', function () { this._set('id', val); });
Zotero.Creator.prototype.__defineSetter__('creatorDataID', function () { this._set('creatorDataID', val); });

Zotero.Creator.prototype._get = function (field) {
	if (this.id && !this._loaded) {
		this.load();
	}
	return this['_' + field];
}

Zotero.Creator.prototype._set = function (field, val) {
	switch (field) {
		case 'id': // set using constructor
		//case 'creatorID': // set using constructor
		case 'creatorDataID':
			throw ("Invalid field '" + field + "' in Zotero.Creator.set()");
	}

	if (this.id) {
		if (!this._loaded) {
			this.load();
		}
	}
	else {
		this._loaded = true;
	}

	this._checkValue(field, val);

	if (this['_' + field] != val) {
		if (!this._changed) {
			this._changed = {};
		}
		this._changed[field] = true;
		if (this.id && this.exists() && !this._previousData) {
			this._previousData = this.serialize();
		}

		this['_' + field] = val;
	}
}

Zotero.Creator.prototype.setFields = function(fields) {
	for (var field in fields) {
		this[field] = fields[field];
	}
}

/**
 * Check if creator exists in the database
 *
 * @return	bool			TRUE if the creator exists, FALSE if not
 */
Zotero.Creator.prototype.exists = function() {
	if (!this.id) {
		throw ('creatorID not set in Zotero.Creator.exists()');
	}

	var sql = "SELECT COUNT(*) FROM creators WHERE creatorID=?";
	return !!Zotero.DB.valueQuery(sql, this.id);
}

Zotero.Creator.prototype.hasChanged = function () {
	return this._changed;
}

Zotero.Creator.prototype.save = function () {
	if (!this.firstName && !this.lastName) {
		throw ('First and last name are empty in Zotero.Creator.save()');
	}

	if (!this.hasChanged()) {
		Zotero.debug("Creator " + this.id + " has not changed");
		return false;
	}

	if (this.fieldMode == 1 && this.firstName) {
		throw ("First name ('" + this.firstName + "') must be empty in single-field mode in Zotero.Creator.save()");
	}

	Zotero.DB.beginTransaction();

	// ID change
	if (this._changed['creatorID']) {
		var oldID = this._previousData.primary.creatorID;
		var params = [this.id, oldID];

		Zotero.debug("Changing creatorID " + oldID + " to " + this.id);

		var row = Zotero.DB.rowQuery("SELECT * FROM creators WHERE creatorID=?", oldID);
		// Add a new row so we can update the old rows despite FK checks
		// Use temp key due to UNIQUE constraint on key column
		Zotero.DB.query("INSERT INTO creators VALUES (?, ?, ?, ?)",
			[this.id, row.creatorDataID, row.dateModified, 'TEMPKEY']);

		Zotero.DB.query("UPDATE itemCreators SET creatorID=? WHERE creatorID=?", params);

		Zotero.DB.query("DELETE FROM creators WHERE creatorID=?", oldID);
		Zotero.DB.query("UPDATE creators SET key=? WHERE creatorID=?", [row.key, this.id]);

		Zotero.Creators.unload(oldID);
		Zotero.Notifier.trigger('id-change', 'creator', oldID + '-' + this.id);

		// update caches
	}

	var isNew = !this.id || !this.exists();

	try {
		// how to know if date modified changed (in server code too?)

		var creatorID = this.id ? this.id : Zotero.ID.get('creators');

		Zotero.debug("Saving creator " + this.id);

		var key = this.key ? this.key : this._generateKey();

		// If this was the only creator with the previous data,
		// see if we can reuse or remove the old data row
		if (this.creatorDataID) {
			var count = Zotero.Creators.countCreatorsWithData(this.creatorDataID);
			if (count == 1) {
				var newCreatorDataID = Zotero.Creators.getDataID(this);
				// Data hasn't changed
				if (this.creatorDataID == newCreatorDataID) {
					var creatorDataID = this.creatorDataID;
				}
				// Existing data row with the new data -- switch to that
				// and delete old row
				else if (newCreatorDataID) {
					var creatorDataID = newCreatorDataID;
					Zotero.Creators.deleteData(this.creatorDataID);
				}
				// Update current data row with new data
				else {
					Zotero.Creators.updateData(this.creatorDataID, this);
					var creatorDataID = this.creatorDataID;
				}
			}
		}

		if (!creatorDataID) {
			var creatorDataID = Zotero.Creators.getDataID(this, true);
		}

		var columns = ['creatorID', 'creatorDataID', 'dateModified', 'key'];
		var placeholders = ['?', '?', '?', '?'];
		var sqlValues = [
			creatorID ? { int: creatorID } : null,
			{ int: creatorDataID },
			// If date modified hasn't changed, use current timestamp
			this._changed.dateModified ?
				this.dateModified : Zotero.DB.transactionDateTime,
			key
		];

		var sql = "REPLACE INTO creators (" + columns.join(', ') + ") VALUES ("
			+ placeholders.join(', ') + ")";
		var insertID = Zotero.DB.query(sql, sqlValues);
		if (!creatorID) {
			creatorID = insertID;
		}

		if (this.id) {
			this.updateLinkedItems();
		}

		Zotero.DB.commitTransaction();
	}
	catch (e) {
		Zotero.DB.rollbackTransaction();
		throw (e);
	}

	// If successful, set values in object
	if (!this.id) {
		this._creatorID = creatorID;
	}
	if (!this.key) {
		this._key = key;
	}
	if (!this.creatorDataID) {
		this._creatorDataID = creatorDataID;
	}

	Zotero.Creators.reload(this.id);

	if (isNew) {
		Zotero.Notifier.trigger('add', 'creator', this.id);
	}
	else {
		Zotero.Notifier.trigger('modify', 'creator', this.id, this._previousData);
	}

	return this.id;
}

Zotero.Creator.prototype.countLinkedItems = function() {
	var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?";
	return Zotero.DB.valueQuery(sql, this.id);
}

Zotero.Creator.prototype.getLinkedItems = function () {
	var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
	return Zotero.DB.columnQuery(sql, this.id);
}

Zotero.Creator.prototype.updateLinkedItems = function () {
	Zotero.DB.beginTransaction();

	var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
	var changedItemIDs = Zotero.DB.columnQuery(sql, this.id);

	if (!changedItemIDs) {
		Zotero.DB.commitTransaction();
		return;
	}

	var notifierData = {};
	for each(var id in changedItemIDs) {
		var item = Zotero.Items.get(id);
		if (item) {
			notifierData[item.id] = { old: item.serialize() };
		}
	}

	sql = "UPDATE items SET dateModified=? WHERE itemID IN "
		+ "(SELECT itemID FROM itemCreators WHERE creatorID=?)";
	Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id]);

	Zotero.Items.reload(changedItemIDs);

	Zotero.DB.commitTransaction();

	Zotero.Notifier.trigger('modify', 'item', changedItemIDs, notifierData);
}

Zotero.Creator.prototype.equals = function (creator) {
	return (creator.firstName == this.firstName) &&
		(creator.lastName == this.lastName) &&
		(creator.fieldMode == this.fieldMode) &&
		(creator.shortName == this.shortName) &&
		(creator.birthYear == this.birthYear);
}

Zotero.Creator.prototype.serialize = function () {
	var obj = {};

	obj.primary = {};
	obj.primary.creatorID = this.id;
	obj.primary.dateModified = this.dateModified;
	obj.primary.key = this.key;

	obj.fields = {};
	if (this.fieldMode == 1) {
		obj.fields.name = this.lastName;
	}
	else {
		obj.fields.firstName = this.firstName;
		obj.fields.lastName = this.lastName;
	}
	obj.fields.fieldMode = this.fieldMode;
	obj.fields.shortName = this.shortName;
	obj.fields.birthYear = this.birthYear;

	return obj;
}

/**
 * Remove creator from all linked items
 *
 * Creators.erase() should be used instead of this
 *
 * Actual deletion of creator occurs in Zotero.Creators.purge(),
 * which is called by Creators.erase()
 */
Zotero.Creator.prototype.erase = function () {
	if (!this.id) {
		return false;
	}

	Zotero.debug("Deleting creator " + this.id);

	// TODO: notifier
	var changedItems = [];
	var changedItemsNotifierData = {};

	Zotero.DB.beginTransaction();

	var toSave = {};

	var linkedItemIDs = this.getLinkedItems();
	for each(var itemID in linkedItemIDs) {
		var item = Zotero.Items.get(itemID)
		if (!item) {
			throw ('Linked item not found in Zotero.Creator.erase()');
		}

		var pos = item.getCreatorPosition(this.id);
		if (!pos) {
			throw ('Creator not found in linked item in Zotero.Creator.erase()');
		}

		item.removeCreator(pos);

		if (!toSave[item.id]) {
			toSave[item.id] = item;
		}
	}

	for each(var item in toSave) {
		item.save();
	}

	Zotero.DB.commitTransaction();
}

// Also called from Zotero.Creators.reload()
Zotero.Creator.prototype.load = function () {
	Zotero.debug("Loading data for creator " + this.id + " in Zotero.Creator.load()");

	if (!this.id) {
		throw ("creatorID not set in Zotero.Creator.load()");
	}

	var sql = "SELECT key, dateModified, creatorDataID, CD.* "
			+ "FROM creators C NATURAL JOIN creatorData CD WHERE creatorID=?";
	var data = Zotero.DB.rowQuery(sql, this.id);

	this._init();
	this._loaded = true;

	if (!data) {
		return;
	}

	for (var key in data) {
		this['_' + key] = data[key];
	}
}

Zotero.Creator.prototype._checkValue = function (field, value) {
	if (this['_' + field] === undefined) {
		throw ("Invalid property " + field + " in Zotero.Creator._checkValue()");
	}

	// Data validation
	switch (field) {
		case 'fieldMode':
			if (value !== 0 && value !== 1) {
				this._invalidValueError(field, value);
			}
			break;

		case 'key':
			var re = /^[23456789ABCDEFGHIJKMNPQRSTUVWXTZ]{8}$/
			if (!re.test(value)) {
				this._invalidValueError(field, value);
			}
			break;

		case 'dateModified':
			if (value !== '' && !Zotero.Date.isSQLDateTime(value)) {
				this._invalidValueError(field, value);
			}
			break;
	}
}

Zotero.Creator.prototype._generateKey = function () {
	return Zotero.ID.getKey();
}

Zotero.Creator.prototype._invalidValueError = function (field, value) {
	throw ("Invalid '" + field + "' value '" + value + "' in Zotero.Creator._invalidValueError()");
}
<|endoftext|>