<commit_before>/*!
 * jquery-time-duration-picker v2.0.0
 *
 * https://github.com/digaev/jquery-time-duration-picker
 *
 * Copyright (c) 2015-2016 Nikolay Digaev
 * Released under the MIT license
 */

( function( $ ) {
  var defaults = {
    lang: "en_US",
    css: {
      position: "fixed" // https://github.com/digaev/jquery-time-duration-picker/issues/1
    },
    years: true,
    months: true,
    days: true,
    hours: true,
    minutes: true,
    seconds: false
  };

  $.timeDurationPicker = {
    defaults: function( options ) {
      var opts = $.extend( true, {}, defaults, options );
      if ( options ) {
        defaults = opts;
      } else {
        return opts;
      }
    },
    langs: {
      en_US: {
        seconds: "Seconds",
        minutes: "Minutes",
        hours: "Hours",
        days: "Days",
        months: "Months",
        years: "Years",
        human_years: "years",
        human_months: "months",
        human_days: "days",
        human_hours: "hours",
        human_minutes: "minutes",
        human_seconds: "seconds",
        and: "and",
        button_ok: "Done"
      }
    }
  };
  $.timeDurationPicker.defaults();
} )( jQuery );

( function( $ ) {
  var instances = [];

  $( document ).focusin( function( e ) {
    for ( var i = 0, c = instances.length; i < c; ++i ) {
      var inst = instances[ i ];
      for ( var j = 0, l = inst.element.length; j < l; ++j ) {
        if ( inst.element[ j ] === e.target ) {
          var offset = $( e.target ).offset();
          offset.top += $( e.target ).outerHeight();
          offset.top -= $( window ).scrollTop();
          offset.left -= $( window ).scrollLeft();
          inst._content.div.css( offset ).fadeIn();
        }
      }
    }
  } ).focusout( function() {

    // FIXME: how to correctly detect that the element has lost focus?
    setTimeout( function() {
      var el = document.activeElement;
      if ( $( el ).parents( ".time-duration-picker-content" ).length === 0 ) {
        for ( var i = 0, c = instances.length; i < c; ++i ) {
          var hide = true;
          var inst = instances[ i ];
          for ( var j = 0, l = inst.element.length; j < l; ++j ) {
            if ( inst.element[ j ] === el ) {
              hide = false;
              break;
            }
          }
          if ( hide ) {
            inst._content.div.fadeOut();
          }
        }
      }
    }, 10 );
  } );

  var YEAR = 12 * 30 * 24 * 60 * 60;
  var MONTH = 30 * 24 * 60 * 60;
  var DAY = 24 * 60 * 60;
  var HOUR = 60 * 60;
  var MINUTE = 60;

  $.widget( "custom.timeDurationPicker", {
    options: {
    },
    _create: function() {
      var self = this;

      this.options = $.extend(
        true, {}, $.timeDurationPicker.defaults(), this.options
      );

      this._content = {};
      this._content.div = $( "
" );
      this._content.div.addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" );
      this._content.div.addClass( "time-duration-picker-content" );
      this._content.div.css( $.extend( {
          display: "none",
          "z-index": 401
      }, this.options.css ) );
      this._content.div.appendTo( document.body );

      this._content.table = $(
        "

"
      ).appendTo( this._content.div );
      this._content.tableBody = $( "tbody", this._content.table );

      this._content.button = $( "<input type='button' />" )
        .val( this._tr( "button_ok" ) );
      this._content.button.addClass(
        "ui-button ui-widget ui-state-default ui-corner-all"
      );
      this._content.button.css( {
        display: "block",
        margin: "0.5em auto",
        padding: "0.5em 1em"
      } );
      this._content.button.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );
      this._content.button.on( "click", function() {
        self._content.div.fadeOut();
        if ( self.options.onSelect ) {
          self.options.onSelect(
            self.element, self.getDuration(), self.getHumanDuration()
          );
        }
      } );
      this._content.button.appendTo( this._content.div );

      this._initUnits();

      instances.push( this );
    },
    _initUnits: function() {
      if ( this.options.years ) {
        this._content.years = this._createSelectWithOptions( 0, 10 );
        this._appendRow( this._tr( "years" ), this._content.years );
      }
      if ( this.options.months ) {
        this._content.months = this._createSelectWithOptions( 0, 11 );
        this._appendRow( this._tr( "months" ), this._content.months );
      }
      if ( this.options.days ) {
        this._content.days = this._createSelectWithOptions( 0, 29 );
        this._appendRow( this._tr( "days" ), this._content.days );
      }
      if ( this.options.hours ) {
        this._content.hours = this._createSelectWithOptions( 0, 23 );
        this._appendRow( this._tr( "hours" ), this._content.hours );
      }
      if ( this.options.minutes ) {
        this._content.minutes = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "minutes" ), this._content.minutes );
      }
      if ( this.options.seconds ) {
        this._content.seconds = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "seconds" ), this._content.seconds );
      }
      if ( this.options.defaultValue ) {
        var value;
        if ( typeof this.options.defaultValue === "function" ) {
          value = this.options.defaultValue.call( this );
        } else {
          value = this.options.defaultValue;
        }
        if ( this.setDuration( value ) ) {
          this.element.val( this.getHumanDuration() );
        }
      }
    },
    _createSelectWithOptions: function( min, max ) {
      var select = $( "<select />" ).addClass(
        "ui-widget ui-state-default ui-corner-all"
      );

      select.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );

      this._createOptionsForSelect( select, min, max );
      return select;
    },
    _createOptionsForSelect: function( select, min, max ) {
      for ( var i = min; i <= max; ++i ) {
        select.append( $( "<option />" ).val( i ).text(
          i < 10 ? ( "0" + i ) : i
        ) );
      }
    },
    _appendRow: function( text, el ) {
      var row = $( "
" ).appendTo( this._content.tableBody );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( $( "" )
        .text( text ) )
        .appendTo( row );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( el )
        .appendTo( row );
    },
    _tr: function( key ) {
      return $.timeDurationPicker.langs[ this.options.lang ][ key ];
    },
    getSeconds: function() {
      return parseInt( this._content.seconds.val() );
    },
    getMinutes: function() {
      return parseInt( this._content.minutes.val() );
    },
    getHours: function() {
      return parseInt( this._content.hours.val() );
    },
    getDays: function() {
      return parseInt( this._content.days.val() );
    },
    getMonths: function() {
      return parseInt( this._content.months.val() );
    },
    getYears: function() {
      return parseInt( this._content.years.val() );
    },
    setDuration: function( value ) {
      value = parseInt( value );
      if ( isNaN( value ) ) {
        return false;
      }

      var i;
      if ( this.options.years ) {
        i = Math.floor( value / YEAR );
        value -= i * YEAR;
        this._content.years.val( i );
      }
      if ( this.options.months ) {
        i = Math.floor( value / MONTH );
        if ( i >= 12 ) {
          i = 0;
        }
        value -= i * MONTH;
        this._content.months.val( i );
      }
      if ( this.options.days ) {
        i = Math.floor( value / DAY );
        if ( i >= 30 ) {
          i = 0;
        }
        value -= i * DAY;
        this._content.days.val( i );
      }
      if ( this.options.hours ) {
        i = Math.floor( value / HOUR );
        if ( i >= 24 ) {
          i = 0;
        }
        value -= i * HOUR;
        this._content.hours.val( i );
      }
      if ( this.options.minutes ) {
        i = Math.floor( value / MINUTE );
        if ( i >= 60 ) {
          i = 0;
        }
        value -= i * MINUTE;
        this._content.minutes.val( i );
      }
      if ( this.options.seconds ) {
        i = Math.floor( value );
        if ( i >= 60 ) {
          i = 0;
        }
        this._content.seconds.val( i );
      }
      return value;
    },
    getDuration: function() {
      var seconds = 0;
      if ( this.options.seconds ) {
        seconds += this.getSeconds();
      }
      if ( this.options.minutes ) {
        seconds += this.getMinutes() * MINUTE;
      }
      if ( this.options.hours ) {
        seconds += this.getHours() * HOUR;
      }
      if ( this.options.days ) {
        seconds += this.getDays() * DAY;
      }
      if ( this.options.months ) {
        seconds += this.getMonths() * MONTH;
      }
      if ( this.options.years ) {
        seconds += this.getYears() * YEAR;
      }
      return seconds;
    },
    getHumanDuration: function() {
      var units = [];
      var duration = "";

      if ( this.options.years && this.getYears() > 0 ) {
        units.push( { value: this.getYears(), name: this._tr( "human_years" ) } );
      }
      if ( this.options.months && this.getMonths() > 0 ) {
        units.push( { value: this.getMonths(), name: this._tr( "human_months" ) } );
      }
      if ( this.options.days && this.getDays() > 0 ) {
        units.push( { value: this.getDays(), name: this._tr( "human_days" ) } );
      }
      if ( this.options.hours && this.getHours() > 0 ) {
        units.push( { value: this.getHours(), name: this._tr( "human_hours" ) } );
      }
      if ( this.options.minutes && this.getMinutes() > 0 ) {
        units.push( { value: this.getMinutes(), name: this._tr( "human_minutes" ) } );
      }
      if ( this.options.seconds && this.getSeconds() > 0 ) {
        units.push( { value: this.getSeconds(), name: this._tr( "human_seconds" ) } );
      }

      for ( var i = 0, l = units.length; i < l; ++i ) {
        var unit = units[ i ];
        var end = ( i === l - 2 ? " " + this._tr( "and" ) + " " : ", " );
        end = i === l - 1 ? "" : end;

        duration += unit.value + " " + unit.name + end;
      }
      return duration;
    }
  } );
} )( jQuery );
<commit_msg>Remove empty lines<commit_after>/*!
 * jquery-time-duration-picker v2.0.0
 *
 * https://github.com/digaev/jquery-time-duration-picker
 *
 * Copyright (c) 2015-2016 Nikolay Digaev
 * Released under the MIT license
 */

( function( $ ) {
  var defaults = {
    lang: "en_US",
    css: {
      position: "fixed" // https://github.com/digaev/jquery-time-duration-picker/issues/1
    },
    years: true,
    months: true,
    days: true,
    hours: true,
    minutes: true,
    seconds: false
  };
  $.timeDurationPicker = {
    defaults: function( options ) {
      var opts = $.extend( true, {}, defaults, options );
      if ( options ) {
        defaults = opts;
      } else {
        return opts;
      }
    },
    langs: {
      en_US: {
        seconds: "Seconds",
        minutes: "Minutes",
        hours: "Hours",
        days: "Days",
        months: "Months",
        years: "Years",
        human_years: "years",
        human_months: "months",
        human_days: "days",
        human_hours: "hours",
        human_minutes: "minutes",
        human_seconds: "seconds",
        and: "and",
        button_ok: "Done"
      }
    }
  };
  $.timeDurationPicker.defaults();
} )( jQuery );

( function( $ ) {
  var instances = [];

  $( document ).focusin( function( e ) {
    for ( var i = 0, c = instances.length; i < c; ++i ) {
      var inst = instances[ i ];
      for ( var j = 0, l = inst.element.length; j < l; ++j ) {
        if ( inst.element[ j ] === e.target ) {
          var offset = $( e.target ).offset();
          offset.top += $( e.target ).outerHeight();
          offset.top -= $( window ).scrollTop();
          offset.left -= $( window ).scrollLeft();
          inst._content.div.css( offset ).fadeIn();
        }
      }
    }
  } ).focusout( function() {

    // FIXME: how to correctly detect that the element has lost focus?
    setTimeout( function() {
      var el = document.activeElement;
      if ( $( el ).parents( ".time-duration-picker-content" ).length === 0 ) {
        for ( var i = 0, c = instances.length; i < c; ++i ) {
          var hide = true;
          var inst = instances[ i ];
          for ( var j = 0, l = inst.element.length; j < l; ++j ) {
            if ( inst.element[ j ] === el ) {
              hide = false;
              break;
            }
          }
          if ( hide ) {
            inst._content.div.fadeOut();
          }
        }
      }
    }, 10 );
  } );

  var YEAR = 12 * 30 * 24 * 60 * 60;
  var MONTH = 30 * 24 * 60 * 60;
  var DAY = 24 * 60 * 60;
  var HOUR = 60 * 60;
  var MINUTE = 60;

  $.widget( "custom.timeDurationPicker", {
    options: {
    },
    _create: function() {
      var self = this;

      this.options = $.extend(
        true, {}, $.timeDurationPicker.defaults(), this.options
      );

      this._content = {};
      this._content.div = $( "
" );
      this._content.div.addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" );
      this._content.div.addClass( "time-duration-picker-content" );
      this._content.div.css( $.extend( {
          display: "none",
          "z-index": 401
      }, this.options.css ) );
      this._content.div.appendTo( document.body );

      this._content.table = $(
        "

"
      ).appendTo( this._content.div );
      this._content.tableBody = $( "tbody", this._content.table );

      this._content.button = $( "<input type='button' />" )
        .val( this._tr( "button_ok" ) );
      this._content.button.addClass(
        "ui-button ui-widget ui-state-default ui-corner-all"
      );
      this._content.button.css( {
        display: "block",
        margin: "0.5em auto",
        padding: "0.5em 1em"
      } );
      this._content.button.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );
      this._content.button.on( "click", function() {
        self._content.div.fadeOut();
        if ( self.options.onSelect ) {
          self.options.onSelect(
            self.element, self.getDuration(), self.getHumanDuration()
          );
        }
      } );
      this._content.button.appendTo( this._content.div );

      this._initUnits();

      instances.push( this );
    },
    _initUnits: function() {
      if ( this.options.years ) {
        this._content.years = this._createSelectWithOptions( 0, 10 );
        this._appendRow( this._tr( "years" ), this._content.years );
      }
      if ( this.options.months ) {
        this._content.months = this._createSelectWithOptions( 0, 11 );
        this._appendRow( this._tr( "months" ), this._content.months );
      }
      if ( this.options.days ) {
        this._content.days = this._createSelectWithOptions( 0, 29 );
        this._appendRow( this._tr( "days" ), this._content.days );
      }
      if ( this.options.hours ) {
        this._content.hours = this._createSelectWithOptions( 0, 23 );
        this._appendRow( this._tr( "hours" ), this._content.hours );
      }
      if ( this.options.minutes ) {
        this._content.minutes = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "minutes" ), this._content.minutes );
      }
      if ( this.options.seconds ) {
        this._content.seconds = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "seconds" ), this._content.seconds );
      }
      if ( this.options.defaultValue ) {
        var value;
        if ( typeof this.options.defaultValue === "function" ) {
          value = this.options.defaultValue.call( this );
        } else {
          value = this.options.defaultValue;
        }
        if ( this.setDuration( value ) ) {
          this.element.val( this.getHumanDuration() );
        }
      }
    },
    _createSelectWithOptions: function( min, max ) {
      var select = $( "<select />" ).addClass(
        "ui-widget ui-state-default ui-corner-all"
      );

      select.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );

      this._createOptionsForSelect( select, min, max );
      return select;
    },
    _createOptionsForSelect: function( select, min, max ) {
      for ( var i = min; i <= max; ++i ) {
        select.append( $( "<option />" ).val( i ).text(
          i < 10 ? ( "0" + i ) : i
        ) );
      }
    },
    _appendRow: function( text, el ) {
      var row = $( "
" ).appendTo( this._content.tableBody );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( $( "" )
        .text( text ) )
        .appendTo( row );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( el )
        .appendTo( row );
    },
    _tr: function( key ) {
      return $.timeDurationPicker.langs[ this.options.lang ][ key ];
    },
    getSeconds: function() {
      return parseInt( this._content.seconds.val() );
    },
    getMinutes: function() {
      return parseInt( this._content.minutes.val() );
    },
    getHours: function() {
      return parseInt( this._content.hours.val() );
    },
    getDays: function() {
      return parseInt( this._content.days.val() );
    },
    getMonths: function() {
      return parseInt( this._content.months.val() );
    },
    getYears: function() {
      return parseInt( this._content.years.val() );
    },
    setDuration: function( value ) {
      value = parseInt( value );
      if ( isNaN( value ) ) {
        return false;
      }

      var i;
      if ( this.options.years ) {
        i = Math.floor( value / YEAR );
        value -= i * YEAR;
        this._content.years.val( i );
      }
      if ( this.options.months ) {
        i = Math.floor( value / MONTH );
        if ( i >= 12 ) {
          i = 0;
        }
        value -= i * MONTH;
        this._content.months.val( i );
      }
      if ( this.options.days ) {
        i = Math.floor( value / DAY );
        if ( i >= 30 ) {
          i = 0;
        }
        value -= i * DAY;
        this._content.days.val( i );
      }
      if ( this.options.hours ) {
        i = Math.floor( value / HOUR );
        if ( i >= 24 ) {
          i = 0;
        }
        value -= i * HOUR;
        this._content.hours.val( i );
      }
      if ( this.options.minutes ) {
        i = Math.floor( value / MINUTE );
        if ( i >= 60 ) {
          i = 0;
        }
        value -= i * MINUTE;
        this._content.minutes.val( i );
      }
      if ( this.options.seconds ) {
        i = Math.floor( value );
        if ( i >= 60 ) {
          i = 0;
        }
        this._content.seconds.val( i );
      }
      return value;
    },
    getDuration: function() {
      var seconds = 0;
      if ( this.options.seconds ) {
        seconds += this.getSeconds();
      }
      if ( this.options.minutes ) {
        seconds += this.getMinutes() * MINUTE;
      }
      if ( this.options.hours ) {
        seconds += this.getHours() * HOUR;
      }
      if ( this.options.days ) {
        seconds += this.getDays() * DAY;
      }
      if ( this.options.months ) {
        seconds += this.getMonths() * MONTH;
      }
      if ( this.options.years ) {
        seconds += this.getYears() * YEAR;
      }
      return seconds;
    },
    getHumanDuration: function() {
      var units = [];
      var duration = "";

      if ( this.options.years && this.getYears() > 0 ) {
        units.push( { value: this.getYears(), name: this._tr( "human_years" ) } );
      }
      if ( this.options.months && this.getMonths() > 0 ) {
        units.push( { value: this.getMonths(), name: this._tr( "human_months" ) } );
      }
      if ( this.options.days && this.getDays() > 0 ) {
        units.push( { value: this.getDays(), name: this._tr( "human_days" ) } );
      }
      if ( this.options.hours && this.getHours() > 0 ) {
        units.push( { value: this.getHours(), name: this._tr( "human_hours" ) } );
      }
      if ( this.options.minutes && this.getMinutes() > 0 ) {
        units.push( { value: this.getMinutes(), name: this._tr( "human_minutes" ) } );
      }
      if ( this.options.seconds && this.getSeconds() > 0 ) {
        units.push( { value: this.getSeconds(), name: this._tr( "human_seconds" ) } );
      }

      for ( var i = 0, l = units.length; i < l; ++i ) {
        var unit = units[ i ];
        var end = ( i === l - 2 ? " " + this._tr( "and" ) + " " : ", " );
        end = i === l - 1 ? "" : end;

        duration += unit.value + " " + unit.name + end;
      }
      return duration;
    }
  } );
} )( jQuery );
<|endoftext|>


<commit_before>var utilities = require('./utilities');

// argv[2] (required): the name of the active file
// argv[3] (required): the ipv4 address of the server to contact first

var activeFileName = process.argv[2];

var fileContents = "";
var tmpFileContents = fileContents;

var otherServerUri = "http://" + process.argv[3] + ':' +
  utilities.SERVER_HTTP_PORT;

var p = new utilities.p2p.peer(otherServerUri, utilities.SERVER_HTTP_PORT);

// TODO: make proxy solution for network with firewall which blocks all ports

utilities.fs.writeFileSync(activeFileName, fileContents);
utilities.fs.writeFileSync(
  activeFileName + utilities.TMP_FILENAME_SUFFIX,
  tmpFileContents);

loadEmacsLisp(utilities.LISP_FILE_PATH, function() {
  openFileInEmacs(activeFileName, function() {
    p.start(
      // client init function
      function() {
        console.log("speaking on " + utilities.os.hostname() +
          ':' +
          utilities.SERVER_HTTP_PORT);
      },

      // client socket function
      function(socket) {
        // get user id, filename, and file contents; write to file
        socket.on('connection_info', function(
          connectionInfo) {
          activeFileName = connectionInfo.activeFileName;
          fileContents = connectionInfo.fileContents;
          tmpFileContents = fileContents;
          utilities.fs.writeFile(
            activeFileName,
            fileContents,
            function(error) {
              if (error) {
                console.log(error);
              }
              updateBufferInEmacs(activeFileName);
              console.log("connection info received");

              socket.on('file_send', function(sentFileBuffer) {
                // if not self socket
                if ("http://127.0.0.1:" + utilities.SERVER_HTTP_PORT !=
                  utilities.p2p.client.getUriOfSocket(
                    socket)) {
                  writeBufferToFile(function() {
                    utilities.fs.readFile(
                      activeFileName,
                      function(error,
                        readFileContents) {
                        if (error) {
                          console.log(error);
                        }
                        utilities.fs.writeFile(
                          activeFileName +
                          utilities.DIFF_FILENAME_SUFFIX,
                          JSON.stringify(utilities.diff_match_patch
                            .patch_make(
                              readFileContents,
                              sentFileBuffer.toString()
                            )),
                          function(error) {
                            performPatchFromFile(
                              activeFileName,
                              activeFileName +
                              utilities.DIFF_FILENAME_SUFFIX
                            );
                            console.log(
                              "file received");
                          }
                        );
                      });
                  });
                }
              });

              socket.on('file_diff', function(sentFilePatch) {
                if ("http://127.0.0.1:" + utilities.SERVER_HTTP_PORT !=
                  utilities.p2p.client.getUriOfSocket(
                    socket)) {
                  utilities.fs.writeFile(
                    activeFileName +
                    utilities.DIFF_FILENAME_SUFFIX,
                    JSON.stringify(sentFilePatch),
                    function(error) {
                      if (error) {
                        console.log(error);
                      }
                      console.log(
                        JSON.stringify(
                          sentFilePatch));
                      performPatchFromFile(
                        activeFileName,
                        activeFileName +
                        utilities.DIFF_FILENAME_SUFFIX
                      );
                      console.log("diff received");
                    });
                }
              });
            });
        });
      },

      // server init function
      function() {
        console.log("listening on " + utilities.os.hostname() + ':' +
          utilities.SERVER_HTTP_PORT);
        // setInterval(broadcastBuffer, utilities.FILE_SYNC_TIME);
        setInterval(broadcastDiff, utilities.DIFF_SYNC_TIME);
      },

      // server socket function
      function(socket) {
        socket.emit('connection_info', {
          activeFileName: activeFileName,
          fileContents: fileContents
        });
      });
  });
});

// requires emacs to be open and server to be on
function openFileInEmacs(filename, callback) {
  var emacsOpenFile = utilities.spawn('emacsclient', ['-n', filename]);
  setupEmacsSpawn(
    emacsOpenFile,
    "error: file could not be loaded",
    "file loaded",
    function() {
      console.log("FILE OPENED :DDDDDDD");
      callback();
    });
}

function broadcastBuffer() {
  var emacsWriteFile = spawnEmacsCommand(
    "send-buffer-to-file", "\"" + activeFileName + "\"",
    "\"" + activeFileName + utilities.TMP_FILENAME_SUFFIX + "\"");
  setupEmacsSpawn(
    emacsWriteFile,
    "error: buffer could not be saved",
    "file broadcasted",
    function() {
      utilities.fs.readFile(
        activeFileName + utilities.TMP_FILENAME_SUFFIX,
        function(error, fileContents) {
          if (error) {
            console.log(error);
          }
          p.emit('file_send', fileContents.toString());
        });
    });
}

function writeBufferToFile(callback) {
  var emacsWriteFile = spawnEmacsCommand(
    "send-buffer-to-file", "\"" + activeFileName + "\"",
    "\"" + activeFileName + "\"");
  setupEmacsSpawn(
    emacsWriteFile,
    "error: buffer could not be saved",
    "file broadcasted",
    function() {
      utilities.fs.readFile(
        activeFileName + utilities.TMP_FILENAME_SUFFIX,
        function(error, fileContents) {
          if (error) {
            console.log(error);
          }
          if ("function" == typeof(callback)) {
            callback();
          }
        });
    });
}

function broadcastDiff() {
  var emacsWriteFile = spawnEmacsCommand(
    "send-buffer-to-file", "\"" + activeFileName + "\"",
    "\"" + activeFileName + utilities.TMP_FILENAME_SUFFIX + "\"");
  setupEmacsSpawn(
    emacsWriteFile,
    "error: buffer could not be loaded",
    "diff broadcasted",
    function() {
      utilities.fs.readFile(
        activeFileName + utilities.TMP_FILENAME_SUFFIX,
        function(tmpError, tmpFileContents) {
          if (tmpError) {
            console.log(tmpError);
            tmpFileContents = "";
          }
          utilities.fs.readFile(
            activeFileName,
            function(curError, curFileContents) {
              if (curError) {
                console.log(curError);
              }
              utilities.fs.writeFile(
                activeFileName,
                tmpFileContents,
                function(error) {
                  if (error) {
                    console.log(error);
                  }
                  p.emit('file_diff',
                    utilities.diff_match_patch.patch_make(
                      curFileContents.toString(),
                      tmpFileContents.toString()));
                }
              );
            });
        });
    });
}

function updateBufferInEmacs(filename, callback) {
  var emacsReadFile = spawnEmacsCommand(
    "read-buffer-from-file", "\"" + filename + "\"");
  setupEmacsSpawn(
    emacsReadFile,
    "error: file could not be read",
    "file read",
    callback);
}

function loadEmacsLisp(filename, callback) {
  var emacsLoadFile = spawnEmacsCommand(
    "load-file", "\"" + filename + "\""
  );
  setupEmacsSpawn(
    emacsLoadFile,
    "error: file could not be loaded",
    "file loaded",
    callback);
}

function performPatchFromFile(filename, callback) {
  var emacsPerformPatch = spawnEmacsCommand(
    "perform-patch-from-file", "\"" + filename + "\"",
    "\"" + filename + utilities.DIFF_FILENAME_SUFFIX + "\""
  );
  setupEmacsSpawn(
    emacsPerformPatch,
    "error: patch could not be performed",
    "file loaded",
    callback);
}

// helper function
function setupEmacsSpawn(spawnProcess, errorText, successText, callback) {
  spawnProcess.stdout.on('data', function(data) {
    console.log("emacs stdout: " + data);
  });
  spawnProcess.stderr.on('data', function(data) {
    console.log("emacs stderr: " + data);
  });
  spawnProcess.on('exit', function(return_code, signal) {
    if (return_code != 0) {
      console.log(errorText);
    } else {
      console.log(successText);
    }
    if ("function" == typeof(callback)) {
      callback();
    }
  });
}

// helper function
// takes variable number of arguments
// returns spawn object
function spawnEmacsCommand() {
  var evalArg = "(";
  for (var i = 0; i < arguments.length; ++i) {
    evalArg += arguments[i] + " ";
  }
  evalArg += ")";
  return utilities.spawn('emacsclient', ['-e', evalArg]);
}
<commit_msg>file transfer<commit_after>var utilities = require('./utilities');

// argv[2] (required): the name of the active file
// argv[3] (required): the ipv4 address of the server to contact first

var activeFileName = process.argv[2];

var fileContents = "";
var tmpFileContents = fileContents;

var otherServerUri = "http://" + process.argv[3] + ':' +
  utilities.SERVER_HTTP_PORT;

var p = new utilities.p2p.peer(otherServerUri, utilities.SERVER_HTTP_PORT);

// TODO: make proxy solution for network with firewall which blocks all ports

utilities.fs.writeFileSync(activeFileName, fileContents);
utilities.fs.writeFileSync(
  activeFileName + utilities.TMP_FILENAME_SUFFIX,
  tmpFileContents);

loadEmacsLisp(utilities.LISP_FILE_PATH, function() {
  openFileInEmacs(activeFileName, function() {
    p.start(
      // client init function
      function() {
        console.log("speaking on " + utilities.os.hostname() + ':' +
          utilities.SERVER_HTTP_PORT);
      },

      // client socket function
      function(socket) {
        // get user id, filename, and file contents; write to file
        socket.on('connection_info', function(
          connectionInfo) {
          activeFileName = connectionInfo.activeFileName;
          fileContents = connectionInfo.fileContents;
          tmpFileContents = fileContents;
          utilities.fs.writeFile(
            activeFileName,
            fileContents,
            function(error) {
              if (error) {
                console.log(error);
              }
              updateBufferInEmacs(activeFileName);
              console.log("connection info received");

              socket.on('file_send', function(sentFileBuffer) {
                // if not self socket
                if ("http://127.0.0.1:" + utilities.SERVER_HTTP_PORT !=
                  utilities.p2p.client.getUriOfSocket(
                    socket)) {
                  writeBufferToFile(function() {
                    utilities.fs.readFile(
                      activeFileName,
                      function(error,
                        readFileContents) {
                        if (error) {
                          console.log(error);
                        }
                        utilities.fs.writeFile(
                          activeFileName +
                          utilities.DIFF_FILENAME_SUFFIX,
                          JSON.stringify(utilities.diff_match_patch
                            .patch_make(
                              readFileContents,
                              sentFileBuffer.toString()
                            )),
                          function(error) {
                            performPatchFromFile(
                              activeFileName,
                              activeFileName +
                              utilities.DIFF_FILENAME_SUFFIX
                            );
                            console.log(
                              "file received");
                          }
                        );
                      });
                  });
                }
              });

              socket.on('file_diff', function(sentFilePatch) {
                if ("http://127.0.0.1:" + utilities.SERVER_HTTP_PORT !=
                  utilities.p2p.client.getUriOfSocket(
                    socket)) {
                  utilities.fs.writeFile(
                    activeFileName +
                    utilities.DIFF_FILENAME_SUFFIX,
                    JSON.stringify(sentFilePatch),
                    function(error) {
                      if (error) {
                        console.log(error);
                      }
                      console.log(
                        JSON.stringify(
                          sentFilePatch));
                      performPatchFromFile(
                        activeFileName,
                        activeFileName +
                        utilities.DIFF_FILENAME_SUFFIX
                      );
                      console.log("diff received");
                    });
                }
              });
            });
        });
      },

      // server init function
      function() {
        console.log("listening on " + utilities.os.hostname() + ':' +
          utilities.SERVER_HTTP_PORT);
        // setInterval(broadcastBuffer, utilities.FILE_SYNC_TIME);
        setInterval(broadcastDiff, utilities.DIFF_SYNC_TIME);
      },

      // server socket function
      function(socket) {
        socket.emit('connection_info', {
          activeFileName: activeFileName,
          fileContents: fileContents
        });
      });
  });
});

// requires emacs to be open and server to be on
function openFileInEmacs(filename, callback) {
  var emacsOpenFile = utilities.spawn('emacsclient', ['-n', filename]);
  setupEmacsSpawn(
    emacsOpenFile,
    "error: file could not be loaded",
    "file loaded",
    function() {
      console.log("FILE OPENED :DDDDDDD");
      callback();
    });
}

function broadcastBuffer() {
  var emacsWriteFile = spawnEmacsCommand(
    "send-buffer-to-file", "\"" + activeFileName + "\"",
    "\"" + activeFileName + utilities.TMP_FILENAME_SUFFIX + "\"");
  setupEmacsSpawn(
    emacsWriteFile,
    "error: buffer could not be saved",
    "file broadcasted",
    function() {
      utilities.fs.readFile(
        activeFileName + utilities.TMP_FILENAME_SUFFIX,
        function(error, fileContents) {
          if (error) {
            console.log(error);
          }
          p.emit('file_send', fileContents.toString());
        });
    });
}

function writeBufferToFile(callback) {
  var emacsWriteFile = spawnEmacsCommand(
    "send-buffer-to-file", "\"" + activeFileName + "\"",
    "\"" + activeFileName + "\"");
  setupEmacsSpawn(
    emacsWriteFile,
    "error: buffer could not be saved",
    "file broadcasted",
    function() {
      utilities.fs.readFile(
        activeFileName + utilities.TMP_FILENAME_SUFFIX,
        function(error, fileContents) {
          if (error) {
            console.log(error);
          }
          if ("function" == typeof(callback)) {
            callback();
          }
        });
    });
}

function broadcastDiff() {
  var emacsWriteFile = spawnEmacsCommand(
    "send-buffer-to-file", "\"" + activeFileName + "\"",
    "\"" + activeFileName + utilities.TMP_FILENAME_SUFFIX + "\"");
  setupEmacsSpawn(
    emacsWriteFile,
    "error: buffer could not be loaded",
    "diff broadcasted",
    function() {
      utilities.fs.readFile(
        activeFileName + utilities.TMP_FILENAME_SUFFIX,
        function(tmpError, tmpFileContents) {
          if (tmpError) {
            console.log(tmpError);
            tmpFileContents = "";
          }
          utilities.fs.readFile(
            activeFileName,
            function(curError, curFileContents) {
              if (curError) {
                console.log(curError);
              }
              utilities.fs.writeFile(
                activeFileName,
                tmpFileContents,
                function(error) {
                  if (error) {
                    console.log(error);
                  }
                  p.emit('file_diff',
                    utilities.diff_match_patch.patch_make(
                      curFileContents.toString(),
                      tmpFileContents.toString()));
                }
              );
            });
        });
    });
}

function updateBufferInEmacs(filename, callback) {
  var emacsReadFile = spawnEmacsCommand(
    "read-buffer-from-file", "\"" + filename + "\"");
  setupEmacsSpawn(
    emacsReadFile,
    "error: file could not be read",
    "file read",
    callback);
}

function loadEmacsLisp(filename, callback) {
  var emacsLoadFile = spawnEmacsCommand(
    "load-file", "\"" + filename + "\""
  );
  setupEmacsSpawn(
    emacsLoadFile,
    "error: file could not be loaded",
    "file loaded",
    callback);
}

function performPatchFromFile(filename, callback) {
  var emacsPerformPatch = spawnEmacsCommand(
    "perform-patch-from-file", "\"" + filename + "\"",
    "\"" + filename + utilities.DIFF_FILENAME_SUFFIX + "\""
  );
  setupEmacsSpawn(
    emacsPerformPatch,
    "error: patch could not be performed",
    "file loaded",
    callback);
}

// helper function
function setupEmacsSpawn(spawnProcess, errorText, successText, callback) {
  spawnProcess.stdout.on('data', function(data) {
    console.log("emacs stdout: " + data);
  });
  spawnProcess.stderr.on('data', function(data) {
    console.log("emacs stderr: " + data);
  });
  spawnProcess.on('exit', function(return_code, signal) {
    if (return_code != 0) {
      console.log(errorText);
    } else {
      console.log(successText);
    }
    if ("function" == typeof(callback)) {
      callback();
    }
  });
}

// helper function
// takes variable number of arguments
// returns spawn object
function spawnEmacsCommand() {
  var evalArg = "(";
  for (var i = 0; i < arguments.length; ++i) {
    evalArg += arguments[i] + " ";
  }
  evalArg += ")";
  return utilities.spawn('emacsclient', ['-e', evalArg]);
}
<|endoftext|>


<commit_before>// Copyright 2019-2020, University of Colorado Boulder

/**
 * An ScrollingChartNode that includes a draggable cursor that allows the user to scrub or play back through the data.
 *
 * @author Jesse Greenberg
 */

import Utils from '../../dot/js/Utils.js';
import merge from '../../phet-core/js/merge.js';
import ArrowNode from '../../scenery-phet/js/ArrowNode.js';
import DragListener from '../../scenery/js/listeners/DragListener.js';
import Circle from '../../scenery/js/nodes/Circle.js';
import Node from '../../scenery/js/nodes/Node.js';
import Rectangle from '../../scenery/js/nodes/Rectangle.js';
import Color from '../../scenery/js/util/Color.js';
import Tandem from '../../tandem/js/Tandem.js';
import griddle from './griddle.js';
import ScrollingChartNode from './ScrollingChartNode.js';

// constants
const WIDTH_PROPORTION = 0.013; // empirically determined
const CURSOR_FILL_COLOR = new Color( 50, 50, 200, 0.2 );
const CURSOR_STROKE_COLOR = Color.DARK_GRAY;
const ARROW_CUE_FILL_COLOR = new Color( 180, 180, 230 );
const ARROW_CUE_STROKE_COLOR = Color.DARK_GRAY;

class XYCursorPlot extends ScrollingChartNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {
    options = merge( {

      // options passed on to the cursor, see ChartCursor
      cursorOptions: null,

      graphPanelOptions: {

        // so that the cursor is draggable
        pickable: true
      },

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    super( options );

    // @private {boolean|null} - if set with setCursorVisible, then this will indicate visibility of the cursor
    this._cursorVisibleOverride = null;

    // @private {number} - value for the cursor, determines cursor positioning
    this.cursorValue = 0;

    // @private - minimum and maximum recorded value, required by the cursor to limit dragging
    this.minRecordedXValue = 0;
    this.maxRecordedXValue = 0;

    // @private Map.<DynamicSeries,function> Keep track of the listener for each series, so the listener can be removed
    // when a series is removed.
    this.dynamicSeriesListenerMap = new Map();

    // @protected (read-only) {DynamicSeries[]}
    this.dynamicSeriesList = [];

    // @private {ChartCursor} - draggable Node that shows the cursor value
    this.chartCursor = new ChartCursor( this, this.modelViewTransformProperty, options.cursorOptions );
    this.graphPanel.addChild( this.chartCursor );

    // initialize position and visibility of the cursor
    this.updateCursor();
  }

  /**
   * Adds a DynamicSeries to the XYCursorPlot.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries
   */
  addDynamicSeries( dynamicSeries ) {
    super.addDynamicSeries( dynamicSeries );

    this.dynamicSeriesList.push( dynamicSeries );

    // when a point is added, update the min and max recorded values
    const dynamicSeriesListener = () => {

      // update the
      this.updateMinMaxRecordedValues();

      // if all data has been removed from the plot, update cursor visibility
      this.updateCursor();
    };

    // save to map so that listener can be found again for disposal
    this.dynamicSeriesListenerMap.set( dynamicSeries, dynamicSeriesListener );
    dynamicSeries.addDynamicSeriesListener( dynamicSeriesListener );
  }

  /**
   * Removes a DynamicSeries from the plot and disposes of its listener.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries - to remove
   */
  removeDynamicSeries( dynamicSeries ) {
    super.removeDynamicSeries( dynamicSeries );

    const seriesIndex = this.dynamicSeriesList.indexOf( dynamicSeries );
    this.dynamicSeriesList.splice( seriesIndex, 1 );

    dynamicSeries.removeDynamicSeriesListener( this.dynamicSeriesListenerMap.get( dynamicSeries ) );
    this.dynamicSeriesListenerMap.delete( dynamicSeries );
  }

  /**
   * Sets the cursor value. The value of the cursor is constrained to be within plot bounds.
   * @public
   *
   * @param {number} value
   */
  setCursorValue( value ) {
    const modelViewTransform = this.modelViewTransformProperty.get();
    const minX = modelViewTransform.viewToModelX( 0 );
    const maxX = modelViewTransform.viewToModelX( this.plotWidth + this.chartCursor.width / 2 );
    this.cursorValue = Utils.clamp( value, minX, maxX );
    this.updateCursor();
  }

  /**
   * Gets the value currently under the cursor.
   * @public
   *
   * @returns {number}
   */
  getCursorValue() {
    return this.cursorValue;
  }

  /**
   * Resets the cursor.
   * @public
   */
  resetCursor() {
    this.chartCursor.reset();
  }

  /**
   * Overrides the default behavior for setting cursor visibility. If set to null, cursor visibility will behave as
   * described in updateCursorVisibility. Otherwise, visibility will equal the boolean value set here.
   * @param {boolean|null} visible
   * @public
   */
  setCursorVisibleOverride( visible ) {
    assert && assert( typeof visible === 'boolean' || visible === null, 'visible must be boolean or null' );
    this._cursorVisibleOverride = visible;
    this.updateCursorVisibility();
  }

  /**
   * Updates the cursor visibility and position.
   * @private
   */
  updateCursor() {
    this.updateCursorVisibility();
    if ( this.chartCursor.isVisible() ) {
      this.updateCursorPosition();
    }
  }

  /**
   * Updates the cursor position.
   * @private
   */
  updateCursorPosition() {
    this.moveCursorToValue( this.cursorValue );
  }

  /**
   * Updates the cursor visibility. The cursor should be visible any time the cursor value is within
   * the recorded value range.
   * @private
   */
  updateCursorVisibility() {

    const wasVisible = this.chartCursor.visible;
    if ( typeof this._cursorVisibleOverride === 'boolean' ) {
      this.chartCursor.setVisible( this._cursorVisibleOverride );
    }
    else {

      const maxX = this.modelViewTransformProperty.get().viewToModelX( this.plotWidth + this.chartCursor.width / 2 );
      const minX = this.modelViewTransformProperty.get().viewToModelX( 0 );

      const isCurrentValueOnChart = ( this.cursorValue >= minX ) && ( this.cursorValue <= maxX );
      const dataExists = this.getDataExists();
      const chartCursorVisible = isCurrentValueOnChart && dataExists;

      this.chartCursor.setVisible( chartCursorVisible );
    }

    // if the cursor just became invisible, interrupt any active dragging
    if ( !this.chartCursor.visible && wasVisible ) {
      this.chartCursor.interruptDrag();
    }
  }

  /**
   * Returns true if any data is attached to this plot.
   * @returns {boolean}
   * @public
   */
  getDataExists() {
    let dataExists = false;
    this.dynamicSeriesListenerMap.forEach( ( listener, dataSeries, map ) => {
      dataExists = dataSeries.hasData();

      // break early
      if ( dataExists ) {
        return true;
      }
    } );

    return dataExists;
  }

  /**
   * Moves the cursor to the specified value.
   * @private
   *
   * @param {number} value
   */
  moveCursorToValue( value ) {
    const viewPosition = this.modelViewTransformProperty.get().modelToViewX( value );

    // keep the cursor within the grid bounds
    this.chartCursor.centerX = Utils.clamp( viewPosition, 0, this.plotWidth );
    this.chartCursor.centerY = this.gridNode.centerY;
  }

  /**
   * From the existing data, update the min and max recorded X values from all of the dynamicSeries of this plot,
   * so that the cursor can be limited to the recorded data.
   * @private
   */
  updateMinMaxRecordedValues() {
    let minValue = Number.POSITIVE_INFINITY;
    let maxValue = Number.NEGATIVE_INFINITY;
    this.dynamicSeriesList.forEach( dynamicSeries => {
      if ( dynamicSeries.getLength() > 0 ) {
        const seriesMinValue = dynamicSeries.getDataPoint( 0 ).x;
        const seriesMaxValue = dynamicSeries.getDataPoint( dynamicSeries.getLength() - 1 ).x;
        if ( seriesMinValue < minValue ) {
          minValue = seriesMinValue;
        }
        if ( seriesMaxValue > maxValue ) {
          maxValue = seriesMaxValue;
        }
      }
    } );

    this.minRecordedXValue = minValue;
    this.maxRecordedXValue = maxValue;
  }
}

/**
 * Rectangular cursor that indicates a current or selected value on the chart.
 */
class ChartCursor extends Rectangle {

  /**
   * @param {ScrollingChartNode} plot
   * @param {Property.<ModelViewTransform2>} modelViewTransformProperty
   * @param {Object} [options]
   */
  constructor( plot, modelViewTransformProperty, options ) {

    options = merge( {
      startDrag: () => {},
      endDrag: () => {},
      drag: () => {},

      // {boolean} - if true, a double headed arrow will be shown to indicate
      // that the cursor is draggable - becomes invisible after first
      // drag
      includeDragCue: false,

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    const width = plot.plotWidth * WIDTH_PROPORTION;
    const height = plot.plotHeight;

    // Set the shape. Origin is at the center top of the rectangle.
    super( 0, -height, width, height, 0, 0, {
      cursor: 'ew-resize',
      fill: CURSOR_FILL_COLOR,
      stroke: CURSOR_STROKE_COLOR,
      lineWidth: 0.4,
      lineDash: [ 4, 4 ]
    } );

    this.plot = plot;
    this.modelViewTransformProperty = modelViewTransformProperty;

    // Make it easier to grab this cursor by giving it expanded mouse and touch areas.
    this.mouseArea = this.localBounds.dilatedX( 12 );
    this.touchArea = this.localBounds.dilatedX( 12 );

    this.includeDragCue = options.includeDragCue;

    if ( this.includeDragCue ) {

      // @private - indicates to the user that the cursor is draggable, only created
      // and added if necessary
      this.dragCueArrowNode = new ArrowNode( -width * 2, 0, width * 2, 0, {
        doubleHead: true,
        headWidth: 12,
        headHeight: 10,
        fill: ARROW_CUE_FILL_COLOR,
        stroke: ARROW_CUE_STROKE_COLOR,
        center: this.center.plusXY( 0, height * 0.4 )
      } );

      this.addChild( this.dragCueArrowNode );
    }

    // Add the indentations that are intended to convey the idea of "gripability".
    const grippyNode = new Node();
    const indentSpacing = height * 0.05;
    for ( let i = 0; i < 3; i++ ) {
      const indentNode = new GrippyIndentNode( width / 2, CURSOR_FILL_COLOR );
      indentNode.top = i * ( indentNode.height + indentSpacing );
      grippyNode.addChild( indentNode );
    }
    grippyNode.center = this.center;
    this.addChild( grippyNode );

    // @private - so that we can interrupt the DragListener if necessary
    this.dragListener = new DragListener( {
      start: ( event, listener ) => {
        assert && assert( this.plot.getDataExists(), 'data should exist for the cursor to be draggable' );
        options.startDrag();
      },
      drag: ( event, listener ) => {
        const parentX = listener.parentPoint.x;
        let newValue = this.modelViewTransformProperty.get().viewToModelX( parentX );

        // limit cursor to the domain of recorded values
        newValue = Utils.clamp( newValue, this.plot.minRecordedXValue, this.plot.maxRecordedXValue );
        this.plot.setCursorValue( newValue );

        options.drag();
      },
      end: ( event, listener ) => {
        options.endDrag();

        // no need to show arrow after user successfully drags the cursor
        if ( this.includeDragCue ) {
          this.dragCueArrowNode.visible = false;
        }
      },
      tandem: options.tandem.createTandem( 'dragListener' )
    } );
    this.addInputListener( this.dragListener );
  }

  /**
   * Interrupt dragging of the cursor, useful when ChartCursor visibility changes.
   * @public
   */
  interruptDrag() {
    this.dragListener.interrupt();
  }

  /**
   * Reset the ChartCursor to its initial state. Note that this does not modify data
   * or the cursor position (cursorValue), only aspects of the view for the cursor itself.
   * @public
   */
  reset() {
    if ( this.includeDragCue ) {
      this.dragCueArrowNode.visible = true;
    }
  }
}

/**
 * This node is meant to portray a small round indentation on a surface.  This is a modern user interface paradigm that
 * is intended to convey the concept of "gripability" (sp?), i.e. something that the user can click on and subsequently
 * grab.  This is meant to look somewhat 3D, much like etched borders do.
 */
class GrippyIndentNode extends Circle {

  /**
   * @param {number} diameter
   * @param {Color} baseColor
   * @param {Object} [options]
   */
  constructor( diameter, baseColor, options ) {

    options = merge( {
      lineWidth: 0.5
    }, options );

    const baseDarkerColor = baseColor.darkerColor( 0.9 );
    const translucentDarkerColor = new Color( baseDarkerColor.getRed(), baseDarkerColor.getGreen(),
      baseDarkerColor.getBlue(), baseColor.getAlpha() );
    const baseLighterColor = baseColor.brighterColor( 0.9 );
    const translucentBrighterColor = new Color( baseLighterColor.getRed(), baseLighterColor.getGreen(),
      baseLighterColor.getBlue(), baseColor.getAlpha() );
    const radius = diameter / 2 - options.lineWidth;

    super( radius, {
      fill: translucentDarkerColor,
      stroke: translucentBrighterColor,
      lineWidth: options.lineWidth
    } );
  }
}

griddle.register( 'XYCursorPlot', XYCursorPlot );
export default XYCursorPlot;<commit_msg>rename dynamicSeriesArray, https://github.com/phetsims/griddle/issues/70<commit_after>// Copyright 2019-2020, University of Colorado Boulder

/**
 * An ScrollingChartNode that includes a draggable cursor that allows the user to scrub or play back through the data.
 *
 * @author Jesse Greenberg
 */

import Utils from '../../dot/js/Utils.js';
import merge from '../../phet-core/js/merge.js';
import ArrowNode from '../../scenery-phet/js/ArrowNode.js';
import DragListener from '../../scenery/js/listeners/DragListener.js';
import Circle from '../../scenery/js/nodes/Circle.js';
import Node from '../../scenery/js/nodes/Node.js';
import Rectangle from '../../scenery/js/nodes/Rectangle.js';
import Color from '../../scenery/js/util/Color.js';
import Tandem from '../../tandem/js/Tandem.js';
import griddle from './griddle.js';
import ScrollingChartNode from './ScrollingChartNode.js';

// constants
const WIDTH_PROPORTION = 0.013; // empirically determined
const CURSOR_FILL_COLOR = new Color( 50, 50, 200, 0.2 );
const CURSOR_STROKE_COLOR = Color.DARK_GRAY;
const ARROW_CUE_FILL_COLOR = new Color( 180, 180, 230 );
const ARROW_CUE_STROKE_COLOR = Color.DARK_GRAY;

class XYCursorPlot extends ScrollingChartNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {
    options = merge( {

      // options passed on to the cursor, see ChartCursor
      cursorOptions: null,

      graphPanelOptions: {

        // so that the cursor is draggable
        pickable: true
      },

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    super( options );

    // @private {boolean|null} - if set with setCursorVisible, then this will indicate visibility of the cursor
    this._cursorVisibleOverride = null;

    // @private {number} - value for the cursor, determines cursor positioning
    this.cursorValue = 0;

    // @private - minimum and maximum recorded value, required by the cursor to limit dragging
    this.minRecordedXValue = 0;
    this.maxRecordedXValue = 0;

    // @private Map.<DynamicSeries,function> Keep track of the listener for each series, so the listener can be removed
    // when a series is removed.
    this.dynamicSeriesListenerMap = new Map();

    // @protected (read-only) {DynamicSeries[]}
    this.dynamicSeriesArray = [];

    // @private {ChartCursor} - draggable Node that shows the cursor value
    this.chartCursor = new ChartCursor( this, this.modelViewTransformProperty, options.cursorOptions );
    this.graphPanel.addChild( this.chartCursor );

    // initialize position and visibility of the cursor
    this.updateCursor();
  }

  /**
   * Adds a DynamicSeries to the XYCursorPlot.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries
   */
  addDynamicSeries( dynamicSeries ) {
    super.addDynamicSeries( dynamicSeries );

    this.dynamicSeriesArray.push( dynamicSeries );

    // when a point is added, update the min and max recorded values
    const dynamicSeriesListener = () => {

      // update the
      this.updateMinMaxRecordedValues();

      // if all data has been removed from the plot, update cursor visibility
      this.updateCursor();
    };

    // save to map so that listener can be found again for disposal
    this.dynamicSeriesListenerMap.set( dynamicSeries, dynamicSeriesListener );
    dynamicSeries.addDynamicSeriesListener( dynamicSeriesListener );
  }

  /**
   * Removes a DynamicSeries from the plot and disposes of its listener.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries - to remove
   */
  removeDynamicSeries( dynamicSeries ) {
    super.removeDynamicSeries( dynamicSeries );

    const seriesIndex = this.dynamicSeriesArray.indexOf( dynamicSeries );
    this.dynamicSeriesArray.splice( seriesIndex, 1 );

    dynamicSeries.removeDynamicSeriesListener( this.dynamicSeriesListenerMap.get( dynamicSeries ) );
    this.dynamicSeriesListenerMap.delete( dynamicSeries );
  }

  /**
   * Sets the cursor value. The value of the cursor is constrained to be within plot bounds.
   * @public
   *
   * @param {number} value
   */
  setCursorValue( value ) {
    const modelViewTransform = this.modelViewTransformProperty.get();
    const minX = modelViewTransform.viewToModelX( 0 );
    const maxX = modelViewTransform.viewToModelX( this.plotWidth + this.chartCursor.width / 2 );
    this.cursorValue = Utils.clamp( value, minX, maxX );
    this.updateCursor();
  }

  /**
   * Gets the value currently under the cursor.
   * @public
   *
   * @returns {number}
   */
  getCursorValue() {
    return this.cursorValue;
  }

  /**
   * Resets the cursor.
   * @public
   */
  resetCursor() {
    this.chartCursor.reset();
  }

  /**
   * Overrides the default behavior for setting cursor visibility. If set to null, cursor visibility will behave as
   * described in updateCursorVisibility. Otherwise, visibility will equal the boolean value set here.
   * @param {boolean|null} visible
   * @public
   */
  setCursorVisibleOverride( visible ) {
    assert && assert( typeof visible === 'boolean' || visible === null, 'visible must be boolean or null' );
    this._cursorVisibleOverride = visible;
    this.updateCursorVisibility();
  }

  /**
   * Updates the cursor visibility and position.
   * @private
   */
  updateCursor() {
    this.updateCursorVisibility();
    if ( this.chartCursor.isVisible() ) {
      this.updateCursorPosition();
    }
  }

  /**
   * Updates the cursor position.
   * @private
   */
  updateCursorPosition() {
    this.moveCursorToValue( this.cursorValue );
  }

  /**
   * Updates the cursor visibility. The cursor should be visible any time the cursor value is within
   * the recorded value range.
   * @private
   */
  updateCursorVisibility() {

    const wasVisible = this.chartCursor.visible;
    if ( typeof this._cursorVisibleOverride === 'boolean' ) {
      this.chartCursor.setVisible( this._cursorVisibleOverride );
    }
    else {

      const maxX = this.modelViewTransformProperty.get().viewToModelX( this.plotWidth + this.chartCursor.width / 2 );
      const minX = this.modelViewTransformProperty.get().viewToModelX( 0 );

      const isCurrentValueOnChart = ( this.cursorValue >= minX ) && ( this.cursorValue <= maxX );
      const dataExists = this.getDataExists();
      const chartCursorVisible = isCurrentValueOnChart && dataExists;

      this.chartCursor.setVisible( chartCursorVisible );
    }

    // if the cursor just became invisible, interrupt any active dragging
    if ( !this.chartCursor.visible && wasVisible ) {
      this.chartCursor.interruptDrag();
    }
  }

  /**
   * Returns true if any data is attached to this plot.
   * @returns {boolean}
   * @public
   */
  getDataExists() {
    let dataExists = false;
    this.dynamicSeriesListenerMap.forEach( ( listener, dataSeries, map ) => {
      dataExists = dataSeries.hasData();

      // break early
      if ( dataExists ) {
        return true;
      }
    } );

    return dataExists;
  }

  /**
   * Moves the cursor to the specified value.
   * @private
   *
   * @param {number} value
   */
  moveCursorToValue( value ) {
    const viewPosition = this.modelViewTransformProperty.get().modelToViewX( value );

    // keep the cursor within the grid bounds
    this.chartCursor.centerX = Utils.clamp( viewPosition, 0, this.plotWidth );
    this.chartCursor.centerY = this.gridNode.centerY;
  }

  /**
   * From the existing data, update the min and max recorded X values from all of the dynamicSeries of this plot,
   * so that the cursor can be limited to the recorded data.
   * @private
   */
  updateMinMaxRecordedValues() {
    let minValue = Number.POSITIVE_INFINITY;
    let maxValue = Number.NEGATIVE_INFINITY;
    this.dynamicSeriesArray.forEach( dynamicSeries => {
      if ( dynamicSeries.getLength() > 0 ) {
        const seriesMinValue = dynamicSeries.getDataPoint( 0 ).x;
        const seriesMaxValue = dynamicSeries.getDataPoint( dynamicSeries.getLength() - 1 ).x;
        if ( seriesMinValue < minValue ) {
          minValue = seriesMinValue;
        }
        if ( seriesMaxValue > maxValue ) {
          maxValue = seriesMaxValue;
        }
      }
    } );

    this.minRecordedXValue = minValue;
    this.maxRecordedXValue = maxValue;
  }
}

/**
 * Rectangular cursor that indicates a current or selected value on the chart.
 */
class ChartCursor extends Rectangle {

  /**
   * @param {ScrollingChartNode} plot
   * @param {Property.<ModelViewTransform2>} modelViewTransformProperty
   * @param {Object} [options]
   */
  constructor( plot, modelViewTransformProperty, options ) {

    options = merge( {
      startDrag: () => {},
      endDrag: () => {},
      drag: () => {},

      // {boolean} - if true, a double headed arrow will be shown to indicate
      // that the cursor is draggable - becomes invisible after first
      // drag
      includeDragCue: false,

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    const width = plot.plotWidth * WIDTH_PROPORTION;
    const height = plot.plotHeight;

    // Set the shape. Origin is at the center top of the rectangle.
    super( 0, -height, width, height, 0, 0, {
      cursor: 'ew-resize',
      fill: CURSOR_FILL_COLOR,
      stroke: CURSOR_STROKE_COLOR,
      lineWidth: 0.4,
      lineDash: [ 4, 4 ]
    } );

    this.plot = plot;
    this.modelViewTransformProperty = modelViewTransformProperty;

    // Make it easier to grab this cursor by giving it expanded mouse and touch areas.
    this.mouseArea = this.localBounds.dilatedX( 12 );
    this.touchArea = this.localBounds.dilatedX( 12 );

    this.includeDragCue = options.includeDragCue;

    if ( this.includeDragCue ) {

      // @private - indicates to the user that the cursor is draggable, only created
      // and added if necessary
      this.dragCueArrowNode = new ArrowNode( -width * 2, 0, width * 2, 0, {
        doubleHead: true,
        headWidth: 12,
        headHeight: 10,
        fill: ARROW_CUE_FILL_COLOR,
        stroke: ARROW_CUE_STROKE_COLOR,
        center: this.center.plusXY( 0, height * 0.4 )
      } );

      this.addChild( this.dragCueArrowNode );
    }

    // Add the indentations that are intended to convey the idea of "gripability".
    const grippyNode = new Node();
    const indentSpacing = height * 0.05;
    for ( let i = 0; i < 3; i++ ) {
      const indentNode = new GrippyIndentNode( width / 2, CURSOR_FILL_COLOR );
      indentNode.top = i * ( indentNode.height + indentSpacing );
      grippyNode.addChild( indentNode );
    }
    grippyNode.center = this.center;
    this.addChild( grippyNode );

    // @private - so that we can interrupt the DragListener if necessary
    this.dragListener = new DragListener( {
      start: ( event, listener ) => {
        assert && assert( this.plot.getDataExists(), 'data should exist for the cursor to be draggable' );
        options.startDrag();
      },
      drag: ( event, listener ) => {
        const parentX = listener.parentPoint.x;
        let newValue = this.modelViewTransformProperty.get().viewToModelX( parentX );

        // limit cursor to the domain of recorded values
        newValue = Utils.clamp( newValue, this.plot.minRecordedXValue, this.plot.maxRecordedXValue );
        this.plot.setCursorValue( newValue );

        options.drag();
      },
      end: ( event, listener ) => {
        options.endDrag();

        // no need to show arrow after user successfully drags the cursor
        if ( this.includeDragCue ) {
          this.dragCueArrowNode.visible = false;
        }
      },
      tandem: options.tandem.createTandem( 'dragListener' )
    } );
    this.addInputListener( this.dragListener );
  }

  /**
   * Interrupt dragging of the cursor, useful when ChartCursor visibility changes.
   * @public
   */
  interruptDrag() {
    this.dragListener.interrupt();
  }

  /**
   * Reset the ChartCursor to its initial state. Note that this does not modify data
   * or the cursor position (cursorValue), only aspects of the view for the cursor itself.
   * @public
   */
  reset() {
    if ( this.includeDragCue ) {
      this.dragCueArrowNode.visible = true;
    }
  }
}

/**
 * This node is meant to portray a small round indentation on a surface.  This is a modern user interface paradigm that
 * is intended to convey the concept of "gripability" (sp?), i.e. something that the user can click on and subsequently
 * grab.  This is meant to look somewhat 3D, much like etched borders do.
 */
class GrippyIndentNode extends Circle {

  /**
   * @param {number} diameter
   * @param {Color} baseColor
   * @param {Object} [options]
   */
  constructor( diameter, baseColor, options ) {

    options = merge( {
      lineWidth: 0.5
    }, options );

    const baseDarkerColor = baseColor.darkerColor( 0.9 );
    const translucentDarkerColor = new Color( baseDarkerColor.getRed(), baseDarkerColor.getGreen(),
      baseDarkerColor.getBlue(), baseColor.getAlpha() );
    const baseLighterColor = baseColor.brighterColor( 0.9 );
    const translucentBrighterColor = new Color( baseLighterColor.getRed(), baseLighterColor.getGreen(),
      baseLighterColor.getBlue(), baseColor.getAlpha() );
    const radius = diameter / 2 - options.lineWidth;

    super( radius, {
      fill: translucentDarkerColor,
      stroke: translucentBrighterColor,
      lineWidth: options.lineWidth
    } );
  }
}

griddle.register( 'XYCursorPlot', XYCursorPlot );
export default XYCursorPlot;<|endoftext|>


<commit_before>var root = require('root')
var JSONStream = require('JSONStream')
var ndjson = require('ndjson')
var through = require('through2')
var pump = require('pump')
var cors = require('cors')
var cookie = require('cookie-signature')
var crypto = require('crypto')
var collect = require('stream-collector')
var auth = require('basic-auth')
var registry = require('./')

var authenticateAll = function(creds, cb) {
  cb(null, 'anon')
}

module.exports = function(opts) {
  if (!opts) opts = {}

  var secret = (opts.secret || crypto.randomBytes(64)).toString()
  var authenticate = opts.authenticate || authenticateAll
  var client = registry()
  var server = root()

  // auth stuff

  var now = function() {
    return (Date.now() / 1000) | 0
  }

  var login = function(req, res) {
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Token')
    res.send({error: 'Requires authorization'})
  }

  var token = function(req, res) {
    var auth = req.headers.authorization
    if (!auth || auth.slice(0, 6) !== 'Token ') return null

    var token = cookie.unsign(auth.slice(6), secret)
    if (!token) return null

    var i = token.lastIndexOf('.')
    if (i === -1) return null

    if (!(Number(token.slice(i+1)) > now())) return null
    return token.slice(0, i)
  }

  // library paths

  server.client = client
  server.setMaxListeners(0)

  client.on('tag', function(id, tag) {
    server.emit('event', {type:'tag', id:id, tag:tag})
  })

  client.on('untag', function(id, tag) {
    server.emit('event', {type:'untag', id:id, tag:tag})
  })

  client.on('image', function(id, data) {
    server.emit('event', {type:'image', id:id})
  })

  client.on('layer', function(id, metadata) {
    server.emit('event', {type:'layer', id:id, checksum:metadata.checksum})
  })

  client.on('verify', function(id) {
    server.emit('event', {type:'verify', id:id})
  })

  server.all(cors())

  server.get('/v1/_ping', function(req, res) {
    res.setHeader('Content-Length', 4)
    res.end('true')
  })

  server.all(function(req, res, next) {
    req.username = token(req)
    if (req.username !== null) {
      if (req.username) server.emit('verify', req.username)
      return next()
    }

    var creds = auth(req)
    if (creds || authenticate === authenticateAll) {
      authenticate(creds, function(err, name) {
        if (err) return login(req, res)
        req.username = name || creds.name || ''
        var token = cookie.sign(req.username+'.'+(now()+6*3600), secret)
        res.setHeader('WWW-Authenticate', 'Token '+token)
        res.setHeader('X-Docker-Token', token)
        res.setHeader('X-Docker-Endpoints', req.headers.host)
        if (req.username) server.emit('login', req.username)
        next()
      })
      return
    }

    login(req, res)
  })

  server.all('/v1/repositories/{name}', '/v1/repositories/library/{name}')
  server.all('/v1/repositories/{name}/images', '/v1/repositories/library/{name}/images')
  server.all('/v1/repositories/{name}/tags/*', '/v1/repositories/library/{name}/tags/{*}')

  server.get('/v1/users', function(req, res) {
    res.send({
      username: req.username
    })
  })

  server.get('/v1/events', function(req, res) {
    var stringify = ndjson.stringify()
    var type = [].concat(req.query.type || [])
    var onevent = function(e) {
      if (!type.length || type.indexOf(e.type) > -1) stringify.write(e)
    }

    server.on('event', onevent)
    req.setTimeout(0)
    pump(stringify, res, function() {
      server.removeListener('event', onevent)
    })
  })

  server.put('/v1/repositories/{namespace}/{name}', function(req, res) {
    req.on('json', function() {
      res.end()
    })
  })

  server.get('/v1/images/{id}/json', function(req, res) {
    client.get(req.params.id, function(err, image, metadata) {
      if (err) return res.error(err)
      res.setHeader('X-Docker-Size', metadata.size)
      res.setHeader('X-Docker-Checksum', metadata.checksum)
      res.send(image)
    })
  })

  server.put('/v1/images/{id}/json', function(req, res) {
    req.on('body', function(image) { // use raw body to ensure JSON hash check works
      client.set(req.params.id, image, function(err) {
        if (err) return res.error(err)
        res.end()
      })
    })
  })

  server.put('/v1/images/{id}/layer', function(req, res) {
    var ws = client.createLayerWriteStream(req.params.id, function(err) {
      if (err) return cb(err)
      res.end()
    })

    req.pipe(ws)
  })

  server.get('/v1/images/{id}/ancestry', function(req, res) {
    pump(
      client.createAncestorStream(req.params.id),
      through.obj(function(data, enc, cb) {
        cb(null, data.id)
      }),
      JSONStream.stringify(),
      res
    )
  })

  server.get('/v1/images/{id}/blobs/*', function(req, res) {
    pump(
      client.createBlobStream(req.params.id, req.params.glob),
      res
    )
  })

  server.get('/v1/images/{id}/tree/*', function(req, res) {
    var dir = req.params.glob
    if (!/\/$/.test(dir)) dir += '/'

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    pump(
      client.createTreeStream(req.params.id, dir),
      JSONStream.stringify(),
      res
    )
  })

  server.put('/v1/images/{id}/checksum', function(req, res) {
    client.verify(req.params.id, req.headers['x-docker-checksum-payload'] || null, function(err, verified) {
      if (err) return res.error(err)
      if (!verified) return res.error(400, 'checksum mismatch') // TODO: is 400 the correct thing to send here?
      res.end()
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/tags', function(req, res) {
    var tags = client.createTagStream(req.params.namespace+'/'+req.params.name)

    pump(
      tags,
      through.obj(function(data, enc, cb) {
        cb(null, [data.tag, data.id])
      }),
      JSONStream.stringifyObject(),
      res
    )
  })

  server.get('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+':'+req.params.tag

    client.resolve(tag, function(err, image) {
      if (err) return res.error(err)
      res.end(image.id)
    })
  })

  server.put('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+':'+req.params.tag

    req.on('json', function(id) {
      client.tag(id, tag, function(err) {
        if (err) return res.error(err)
        res.end()
      })
    })
  })

  server.del('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+':'+req.params.tag

    collect(client.createTagStream(tag), function(err, tags) {
      if (err) return res.error(err)
      if (!tags.length) return res.send("")

      client.untag(tags[0].id, tag, function(err) {
        if (err) return res.error(error)
        res.send("")
      })
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/images', function(req, res) { // wat?
    res.send([])
  })

  server.put('/v1/repositories/{namespace}/{name}/images', function(req, res) { // wat?
    req.on('json', function(data) {
      res.statusCode = 204
      res.end()
    })
  })

  server.get('/v1/repositories', function(req, res) {
    pump(
      client.createTagStream(),
      through.obj(function(data, enc, cb) {
        cb(null, [data.name, data.id])
      }),
      JSONStream.stringifyObject(),
      res
    )
  })

  server.get('/', function(req, res) {
    res.send({
      name: 'docker-registry-server',
      version: require('./package.json').version
    })
  })

  server.error(function(req, res, err) {
    if (err.status) res.statusCode = err.status
    if (res.statusCode !== 404) console.error('Error: %s (%d)', err.message, res.statusCode)
    res.send({
      error: err.message,
      status: err.status
    })
  })

  return server
}
<commit_msg>fix layer streams<commit_after>var root = require('root')
var JSONStream = require('JSONStream')
var ndjson = require('ndjson')
var through = require('through2')
var pump = require('pump')
var cors = require('cors')
var cookie = require('cookie-signature')
var crypto = require('crypto')
var collect = require('stream-collector')
var auth = require('basic-auth')
var registry = require('./')

var authenticateAll = function(creds, cb) {
  cb(null, 'anon')
}

module.exports = function(opts) {
  if (!opts) opts = {}

  var secret = (opts.secret || crypto.randomBytes(64)).toString()
  var authenticate = opts.authenticate || authenticateAll
  var client = registry()
  var server = root()

  // auth stuff

  var now = function() {
    return (Date.now() / 1000) | 0
  }

  var login = function(req, res) {
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Token')
    res.send({error: 'Requires authorization'})
  }

  var token = function(req, res) {
    var auth = req.headers.authorization
    if (!auth || auth.slice(0, 6) !== 'Token ') return null

    var token = cookie.unsign(auth.slice(6), secret)
    if (!token) return null

    var i = token.lastIndexOf('.')
    if (i === -1) return null

    if (!(Number(token.slice(i+1)) > now())) return null
    return token.slice(0, i)
  }

  // library paths

  server.client = client
  server.setMaxListeners(0)

  client.on('tag', function(id, tag) {
    server.emit('event', {type:'tag', id:id, tag:tag})
  })

  client.on('untag', function(id, tag) {
    server.emit('event', {type:'untag', id:id, tag:tag})
  })

  client.on('image', function(id, data) {
    server.emit('event', {type:'image', id:id})
  })

  client.on('layer', function(id, metadata) {
    server.emit('event', {type:'layer', id:id, checksum:metadata.checksum})
  })

  client.on('verify', function(id) {
    server.emit('event', {type:'verify', id:id})
  })

  server.all(cors())

  server.get('/v1/_ping', function(req, res) {
    res.setHeader('Content-Length', 4)
    res.end('true')
  })

  server.all(function(req, res, next) {
    req.username = token(req)
    if (req.username !== null) {
      if (req.username) server.emit('verify', req.username)
      return next()
    }

    var creds = auth(req)
    if (creds || authenticate === authenticateAll) {
      authenticate(creds, function(err, name) {
        if (err) return login(req, res)
        req.username = name || creds.name || ''
        var token = cookie.sign(req.username+'.'+(now()+6*3600), secret)
        res.setHeader('WWW-Authenticate', 'Token '+token)
        res.setHeader('X-Docker-Token', token)
        res.setHeader('X-Docker-Endpoints', req.headers.host)
        if (req.username) server.emit('login', req.username)
        next()
      })
      return
    }

    login(req, res)
  })

  server.all('/v1/repositories/{name}', '/v1/repositories/library/{name}')
  server.all('/v1/repositories/{name}/images', '/v1/repositories/library/{name}/images')
  server.all('/v1/repositories/{name}/tags/*', '/v1/repositories/library/{name}/tags/{*}')

  server.get('/v1/users', function(req, res) {
    res.send({
      username: req.username
    })
  })

  server.get('/v1/events', function(req, res) {
    var stringify = ndjson.stringify()
    var type = [].concat(req.query.type || [])
    var onevent = function(e) {
      if (!type.length || type.indexOf(e.type) > -1) stringify.write(e)
    }

    server.on('event', onevent)
    req.setTimeout(0)
    pump(stringify, res, function() {
      server.removeListener('event', onevent)
    })
  })

  server.put('/v1/repositories/{namespace}/{name}', function(req, res) {
    req.on('json', function() {
      res.end()
    })
  })

  server.get('/v1/images/{id}/json', function(req, res) {
    client.get(req.params.id, function(err, image, metadata) {
      if (err) return res.error(err)
      res.setHeader('X-Docker-Size', metadata.size)
      res.setHeader('X-Docker-Checksum', metadata.checksum)
      res.send(image)
    })
  })

  server.put('/v1/images/{id}/json', function(req, res) {
    req.on('body', function(image) { // use raw body to ensure JSON hash check works
      client.set(req.params.id, image, function(err) {
        if (err) return res.error(err)
        res.end()
      })
    })
  })

  server.get('/v1/images/{id}/layer', function(req, res) {
    pump(client.createLayerReadStream(req.params.id), res)
  })

  server.put('/v1/images/{id}/layer', function(req, res) {
    var ws = client.createLayerWriteStream(req.params.id, function(err) {
      if (err) return cb(err)
      res.end()
    })

    pump(req, ws)
  })

  server.get('/v1/images/{id}/ancestry', function(req, res) {
    pump(
      client.createAncestorStream(req.params.id),
      through.obj(function(data, enc, cb) {
        cb(null, data.id)
      }),
      JSONStream.stringify(),
      res
    )
  })

  server.get('/v1/images/{id}/blobs/*', function(req, res) {
    pump(
      client.createBlobStream(req.params.id, req.params.glob),
      res
    )
  })

  server.get('/v1/images/{id}/tree/*', function(req, res) {
    var dir = req.params.glob
    if (!/\/$/.test(dir)) dir += '/'

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    pump(
      client.createTreeStream(req.params.id, dir),
      JSONStream.stringify(),
      res
    )
  })

  server.put('/v1/images/{id}/checksum', function(req, res) {
    client.verify(req.params.id, req.headers['x-docker-checksum-payload'] || null, function(err, verified) {
      if (err) return res.error(err)
      if (!verified) return res.error(400, 'checksum mismatch') // TODO: is 400 the correct thing to send here?
      res.end()
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/tags', function(req, res) {
    var tags = client.createTagStream(req.params.namespace+'/'+req.params.name)

    pump(
      tags,
      through.obj(function(data, enc, cb) {
        cb(null, [data.tag, data.id])
      }),
      JSONStream.stringifyObject(),
      res
    )
  })

  server.get('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+':'+req.params.tag

    client.resolve(tag, function(err, image) {
      if (err) return res.error(err)
      res.end(image.id)
    })
  })

  server.put('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+':'+req.params.tag

    req.on('json', function(id) {
      client.tag(id, tag, function(err) {
        if (err) return res.error(err)
        res.end()
      })
    })
  })

  server.del('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+':'+req.params.tag

    collect(client.createTagStream(tag), function(err, tags) {
      if (err) return res.error(err)
      if (!tags.length) return res.send("")

      client.untag(tags[0].id, tag, function(err) {
        if (err) return res.error(error)
        res.send("")
      })
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/images', function(req, res) { // wat?
    res.send([])
  })

  server.put('/v1/repositories/{namespace}/{name}/images', function(req, res) { // wat?
    req.on('json', function(data) {
      res.statusCode = 204
      res.end()
    })
  })

  server.get('/v1/repositories', function(req, res) {
    pump(
      client.createTagStream(),
      through.obj(function(data, enc, cb) {
        cb(null, [data.name, data.id])
      }),
      JSONStream.stringifyObject(),
      res
    )
  })

  server.get('/', function(req, res) {
    res.send({
      name: 'docker-registry-server',
      version: require('./package.json').version
    })
  })

  server.error(function(req, res, err) {
    if (err.status) res.statusCode = err.status
    if (res.statusCode !== 404) console.error('Error: %s (%d)', err.message, res.statusCode)
    res.send({
      error: err.message,
      status: err.status
    })
  })

  return server
}
<|endoftext|>