Re-added ability to skip Scenarios if first argument is false.

--- cakes.coffee
+++ cakes.coffee
@@ -66,23 +66,26 @@

 	return (message, callback)->

+		if message == false  # First arg is false, skip
+			message = callback
+			return skipScenario message, options
+
 		if 'label' in options
 			message = 'Scenario: '+message
 		if 'whitespace' in options
 			message = '\n    '+message
 		if 'style' in options
 			message = message.green
-		if 'skippable' in options  # TODO
-			###
-			unless arguments[0]
-				arguments = _.toArray(arguments)
-				arguments.shift()  # removes false
-				arguments[1] = ->  # removes call body
-				arguments[0] = ('(skipped) '+arguments[0]).yellow.bold
-			###
-

 		mocha.describe message, callback
+
+skipScenario = (message, options)->
+	if 'label' in options
+		message = '(skipped) ' + message
+	if 'style' in options
+		message = message.yellow.bold
+
+	mocha.describe message, ->

 exports.Scenario = createScenario(['whitespace', 'label', 'style'])


Fixed zappa.app options defaults.

Fixed zappa.app options defaults.

(Found using the documentation test suite.)

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -51,7 +51,7 @@
       recipient[k] = v unless recipient[k]

 # Takes in a function and builds express/socket.io apps based on the rules contained in it.
-zappa.app = (func,options) ->
+zappa.app = (func,options={}) ->
   context = {zappa, express}

   context.root = path.dirname(module.parent.filename)
@@ -158,6 +158,7 @@
       css = String(v)
       route verb: 'get', path: k, handler: css, contentType: 'css'

+  options.require_css ?= []
   if typeof options.require_css is 'string'
     options.require_css = [options.require_css]
   for name in options.require_css


Treasure Map was broken. The line I originally wrote for trashing the second map was:

doTrash(c['Treasure Map'])

which was changed to:

state.doTrash(state.current, c['Treasure Map']),

but this was still incorrect. It is now:

state.current.doTrash(c['Treasure Map'])

I discovered it was broken by testing each card I've submitted on the live site, and when the AI got to a hand with two T Maps, it played one then the simulator just stopped working.

I have also added code so it will properly deal with the case where the number of Golds in the supply is less than 4, and it will output to the log the trashing of both Maps.

--- cards.coffee
+++ cards.coffee
@@ -867,7 +867,7 @@
     state.tradeRouteValue
 }

-makeCard 'Treasure Map', action {
+makeCard 'Treasure Map', action, {
   cost: 4

   playEffect: (state) ->
@@ -875,17 +875,22 @@

     if c['Treasure Map'] in state.current.inPlay
       state.current.inPlay.remove(c['Treasure Map'])
+      state.log("...trashing the Treasure Map.")
       trashedMaps += 1

     if c['Treasure Map'] in state.current.hand
-      doTrash(c['Treasure Map'])
+      state.current.doTrash(c['Treasure Map'])
+      state.log("...and trashing another Treasure Map.")
       trashedMaps += 1

     if trashedMaps == 2
+      numGolds = 0
       for num in [1..4]
-        state.gainCard(state.current, c.Gold, true)
-        transferCardToTop(c.Gold, state.current.discard, state.current.draw)
-      state.log("…gaining 4 Golds, putting them on top of the deck.")
+        if state.countInSupply(c.Gold) > 0
+          state.gainCard(state.current, c.Gold, true)
+          transferCardToTop(c.Gold, state.current.discard, state.current.draw)
+          numGolds += 1
+      state.log("…gaining #{numGolds} Golds, putting them on top of the deck.")
 }

 makeCard "Trusty Steed", c["Bag of Gold"], {


Set required attribute when a required field is deleted during editing

#561

--- assets/js/views/image-upload/preview.coffee
+++ assets/js/views/image-upload/preview.coffee
@@ -47,6 +47,7 @@
     # Replace the file input with a clone because you can't clear a file field
     @$file.replaceWith @$file = @$file.clone(true)
     @$file.trigger 'change'
+    @$file.prop('required', true) if @$file.hasClass('required')

     # Add a hidden field of the same name that tells Decoy to delete the
     # previous.


Client: Teams: Added resendInvitations method. it invokes if resendInvitation button clicked.

--- client/admin/lib/views/invitations/invitesomeoneview.coffee
+++ client/admin/lib/views/invitations/invitesomeoneview.coffee
@@ -176,6 +176,29 @@
     @resendInvitationConfirmModal?.destroy()

+  resendInvitations: (invites, newInvitations) ->
+
+    title    = 'Invitation is resent.'
+    title    = 'Invitations are resent.'  if invites.length > 1
+
+    queue = invites.map (invite) => (next) =>
+
+      remote.api.JInvitation.sendInvitationByCode invite.code, (err) =>
+        if err
+        then next err
+        else next()
+
+    async.series queue, (err) =>
+
+      view.destroy()  for view in @inputViews by -1
+      @createInitialInputs()
+
+      duration = 5000
+      unless newInvitations.length
+        title  = "Invitation is resent to <strong>#{invites[0].email}</strong>"
+        title  = "All invitations are resent."  if invites.length > 1
+        return new KDNotificationView { title, duration }
+

     remote.api.JInvitation.create invitations: invites, (err) =>
       if err


added a clearfix hack

added a clearfix hack

--- client/app/Applications/Groups.kdapplication/AppView.coffee
+++ client/app/Applications/Groups.kdapplication/AppView.coffee
@@ -287,7 +287,7 @@

 class GroupsInvitationRequestListItemView extends KDListItemView
   constructor:(options, data)->
-    options.cssClass = 'invitation-request formline'
+    options.cssClass = 'invitation-request formline clearfix'

     super


[Banning] set hostKite as null instead of empty string

--- client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -55,7 +55,7 @@
                     activityController.emit "ActivityItemBlockUserClicked", accounts[0].profile.nickname
                   else
                     new KDNotificationView {title: "Please select an account!"}
-
+
               Impersonate     :
                 label         : "Switch to User "
                 itemClass     : KDButtonView


Agent pie testing

Agent pie testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -134,7 +134,6 @@
         labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
             p1.startAngle - p2.startAngle
         ))
-        #labels.enter().append('text').attr 'text-anchor', 'middle'
         labels.enter().append('text').attr('text-anchor', 'middle').attr("filter","url(#dropshadow)")
         #labels.enter().append('text')
             #.attr('text-anchor', (d) ->
@@ -248,6 +247,7 @@
             return
         )
         .transition()
+            .delay(1000)
             .attr('x', (d) ->
                 d.x
             ).attr 'y', (d) ->


We don't need money anymore.

We don't need money anymore.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -42,9 +42,6 @@
         disk            :
           type          : Number
           default       : 0.5
-        money           :
-          type          : Number
-          default       : 0
       isEnabled         :
         type            : Boolean
         default         : yes
@@ -113,7 +110,7 @@
   #     target.addLimit limit, 'vm', (err)->
   #       callback err ? null, unless err then limit

-  @getUsageTemplate = -> { cpu: 0, ram: 0, disk: 0, money: 0 }
+  @getUsageTemplate = -> { cpu: 0, ram: 0, disk: 0 }

   @calculateUsage = (account, groupSlug, callback)->
     nickname =


Delete VM by Name

Delete VM by Name

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -17,10 +17,14 @@
     permissions         :
       'sudoer'          : []
       'create vms'      : ['member','moderator']
+      'delete vms'      : ['member','moderator']
       'list all vms'    : ['member','moderator']
       'list default vm' : ['member','moderator']
     sharedMethods       :
-      static            : ['fetchVms','fetchVmsByContext','calculateUsage']
+      static            : [
+                           'fetchVms','fetchVmsByContext','calculateUsage'
+                           'removeByName'
+                          ]
       instance          : []
     schema              :
       ip                :
@@ -168,13 +172,27 @@
       {delegate} = client.connection
       @fetchAccountVmsBySelector delegate, {}, options, callback

-
     # TODO: let's implement something like this:
     # failure: (client, callback) ->
     #   @fetchDefaultVmByContext client, (err, vm)->
     #     return callback err  if err
     #     callback null, [vm]

+  @removeByName = permit 'delete vms',
+    success:(client, vmName, callback)->
+      {delegate} = client.connection
+
+      delegate.fetchUser (err, user) ->
+        return callback err  if err
+
+        selector =
+          name   : vmName
+          users  : { $elemMatch: id: user.getId(), owner: yes }
+
+        JVM.one selector, (err, vm)->
+          return callback err  if err
+          return callback new KodingError 'No such VM'  unless vm
+          vm.remove callback

   do ->


Remove percentages and combine robot.hear calls

--- scripts/arnie-quotes.coffee
+++ scripts/arnie-quotes.coffee
@@ -10,8 +10,6 @@
 # Author:
 #   Casey Lawrence <casey.j.lawrence@gmail.com>
 #
-
-odds  = [1...100]

 arnie_quotes = [
     'GET TO THE CHOPPA!',
@@ -28,12 +26,5 @@
 ]

 module.exports = (robot) ->
-    robot.hear /(^|\s)arnie(\s|$|[\W])/ig, (msg) ->
-        val = msg.random odds
-        if val > 0 # Set Arnie to reply 100% for now
-            msg.send msg.random arnie_quotes
-
-    robot.hear /(^|\s)arnold(\s|$|[\W])/ig, (msg) ->
-        val = msg.random odds
-        if val > 0 # Set Arnie to reply 100% for now
-            msg.send msg.random arnie_quotes+    robot.hear /(^|\s)(arnold|arnie)(\s|$|[\W])/ig, (msg) ->
+        msg.send msg.random arnie_quotes


changed default outgoing content-type to json

--- modules/http/platforms/node/tools/index.coffee
+++ modules/http/platforms/node/tools/index.coffee
@@ -31,14 +31,20 @@
     ##
     ##
     post: (options, data, fn)->
-        if typeof data is 'object'
-            data = qs.stringify data

         tools.parseOptions options, (err, options)->

             options.method ?= 'POST'
             options.headers ?= {}
-            options.headers['Content-Type'] = 'application/x-www-form-urlencoded'
+            options.headers['Content-Type'] ?= 'application/json'
+
+            if typeof data isnt 'string'
+                if options.headers['Content-Type'].toLowerCase().match 'json'
+                    data = JSON.stringify data
+                else
+                    data = qs.stringify data
+
+            data = new Buffer data
             options.headers['Content-Length'] = data.length

             #console.log options


Notification: Add cleanchatnotification task

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -389,6 +389,7 @@
         make configure
         # make install
         cd #{projectRoot}
+        cleanchatnotifications

         echo '#---> AUTHORIZING THIS COMPUTER WITH MATCHING KITE.KEY (@farslan) <---#'
         mkdir $HOME/.kite &>/dev/null
@@ -701,6 +702,11 @@
         go run ./go/src/socialapi/workers/migrator/main.go -c #{socialapi.configFilePath}
       }

+      function cleanchatnotifications () {
+        cd #{GOBIN}
+        ./notification -c #{socialapi.configFilePath} -h
+      }
+
       function sandbox_buildservices () {
         SANDBOX_SERVICES=54.165.122.100
         SANDBOX_WEB_1=54.165.177.88
@@ -782,6 +788,9 @@
       elif [ "$1" == "importusers" ]; then
         importusers

+      elif [ "$1" == "cleanchatnotifications" ]; then
+        cleanchatnotifications
+
       elif [ "$1" == "worker" ]; then

         if [ "$2" == "" ]; then


get rid of overzealeous logging in up.magic

--- lib/assets/javascripts/up/magic.js.coffee
+++ lib/assets/javascripts/up/magic.js.coffee
@@ -74,13 +74,13 @@
       callback: awakener

   compile = ($fragment) ->
-    console.log("Compiling fragment", $fragment, "with", awakeners)
+    # console.log("Compiling fragment", $fragment, "with", awakeners)
     for awakener in awakeners
-      console.log("running", awakener.selector, "on", $fragment)
+      # console.log("running", awakener.selector, "on", $fragment)
       util.findWithSelf($fragment, awakener.selector).each ->
         $element = $(this)
         destroyer = awakener.callback.apply(this, [$element])
-        console.log("got destroyer", destroyer)
+        # console.log("got destroyer", destroyer)
         if util.isFunction(destroyer)
           $element.addClass(DESTROYABLE_CLASS)
           $element.data(DESTROYER_KEY, destroyer)


Use subscribeToCommmand for editor view commands

Closes #39

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -29,13 +29,13 @@
     @list.on 'mousewheel', (event) -> event.stopPropagation()

     @editorView.on 'editor:path-changed', => @setCurrentBuffer(@editor.getBuffer())
-    @editorView.command 'autocomplete:toggle', =>
+    @subscribeToCommand @editorView, 'autocomplete:toggle', =>
       if @hasParent()
         @cancel()
       else
         @attach()
-    @editorView.command 'autocomplete:next', => @selectNextItemView()
-    @editorView.command 'autocomplete:previous', => @selectPreviousItemView()
+    @subscribeToCommand @editorView, 'autocomplete:next', => @selectNextItemView()
+    @subscribeToCommand @editorView, 'autocomplete:previous', => @selectPreviousItemView()

     @filterEditorView.preempt 'textInput', ({originalEvent}) =>
       text = originalEvent.data


Also update XML on click to catch selections.

--- client/hook-field.coffee
+++ client/hook-field.coffee
@@ -11,3 +11,4 @@
           n.text v
   elm.change h
   elm.blur h
+  elm.click h


Fix incorrect filtering.

Fix incorrect filtering.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -564,9 +564,15 @@
   # Filter link hints by search string, renumbering the hints as necessary.
   filterLinkHints: (hintMarkers) ->
     idx = 0
-    linkSearchString = @linkTextKeystrokeQueue.join("").toLowerCase()
+    linkSearchString = @linkTextKeystrokeQueue.join("").trim().toLowerCase()
+    return hintMarkers unless 0 < linkSearchString.length
+
     do (scoreFunction = @scoreLinkHint linkSearchString) ->
       linkMarker.score = scoreFunction linkMarker for linkMarker in hintMarkers
+    # The Javascript sort() method is known not to be stable.  Nevertheless, we require (and assume, here)
+    # that it is deterministic.  So, if the user is typing hint characters, then hints will always end up in
+    # the same order and hence with the same hint strings (because hint-string filtering happens after the
+    # filtering here).
     hintMarkers = hintMarkers[..].sort (a,b) -> b.score - a.score

     for linkMarker in hintMarkers
@@ -577,7 +583,6 @@

   # Assign a score to a filter match (higher is better).  We assign a higher score for matches at the start of
   # a word, and a considerably higher score still for matches which are whole words.
-  # Note(smblott) if linkSearchString is empty, then every hint get a score of 4.
   scoreLinkHint: (linkSearchString) ->
     searchWords = linkSearchString.trim().split /\s+/
     (linkMarker) ->


add tests for the previous commit

add tests for the previous commit


added "()[]{}" after "Highlight matching brackets"

--- client/prefs-code.coffee
+++ client/prefs-code.coffee
@@ -9,8 +9,8 @@
   $e.html '''

<input id=code-ai   type=checkbox>Auto-indent <input id=code-sw  size=1> spaces

<input id=code-aim  type=checkbox>in methods: <input id=code-swm size=1> spaces
-
<input id=code-mb   type=checkbox>Highlight matching brackets

-
<input id=code-acbr type=checkbox>Auto-close brackets: <tt>{}[]()</tt>
+
<input id=code-mb   type=checkbox>Highlight matching brackets: <tt>()[]{}</tt>

+
<input id=code-acbr type=checkbox>Auto-close brackets: <tt>()[]{}</tt>

<input id=code-acbl type=checkbox>Auto-close blocks: <tt>:If :For ...</tt>
        with <select id=code-acbe>
          <option value=0>:EndIf,:EndFor,...


Package update

Package update

 - Debug messages
 - WebWorker usage for file reading (avoid thread blocking)
 - `estimateTime` ReactiveVar
 - `estimateSpeed` ReactiveVar
 - `onError` callback
 - Better `dynamic` logic for `chunkSize` and `streams`
 - Fix all issues about uploading large files

--- files.coffee
+++ files.coffee
@@ -68,67 +68,86 @@
 @class
 @namespace Meteor
 @name Files
-@param config           {Object}   - Configuration object with next properties:
-@param config.debug     {Boolean}  - Turn on/of debugging and extra logging
-@param config.schema    {Object}   - Collection Schema
-@param config.public    {Boolean}  - Store files in folder accessible for proxy servers, for limits, and more - read docs
-@param config.strict    {Boolean}  - Strict mode for partial content, if is `true` server will return `416` response code, when `range` is not specified, otherwise server return `206`
-@param config.protected {Function} - If `true` - files will be served only to authorized users, if `function()` - you're able to check visitor's permissions in your own way function's context has:
+@param config           {Object}   - [Both]   Configuration object with next properties:
+@param config.debug     {Boolean}  - [Both]   Turn on/of debugging and extra logging
+@param config.schema    {Object}   - [Both]   Collection Schema
+@param config.public    {Boolean}  - [Both]   Store files in folder accessible for proxy servers, for limits, and more - read docs
+@param config.strict    {Boolean}  - [Server] Strict mode for partial content, if is `true` server will return `416` response code, when `range` is not specified, otherwise server return `206`
+@param config.protected {Function} - [Both]   If `true` - files will be served only to authorized users, if `function()` - you're able to check visitor's permissions in your own way function's context has:
   - `request` - On server only
   - `response` - On server only
   - `user()`
   - `userId`
-@param config.chunkSize      {Number}  - Upload chunk size, default: 524288 bytes (0,5 Mb)
-@param config.permissions    {Number}  - Permissions which will be set to uploaded files, like: `511` or `0o755`
-@param config.storagePath    {String}  - Storage path on file system
-@param config.cacheControl   {String}  - Default `Cache-Control` header
-@param config.throttle       {Number}  - bps throttle threshold
-@param config.downloadRoute  {String}  - Server Route used to retrieve files
-@param config.collectionName {String}  - Collection name
-@param config.namingFunction {Function}- Function which returns `String`
-@param config.integrityCheck {Boolean} - Check file's integrity before serving to users
-@param config.onBeforeUpload {Function}- Function which executes on server after receiving each chunk and on client right before beginning upload. Function context is `File` - so you are able to check for extension, mime-type, size and etc.
+@param config.chunkSize      {Number}  - [Both] Upload chunk size, default: 524288 bytes (0,5 Mb)
+@param config.permissions    {Number}  - [Server] Permissions which will be set to uploaded files, like: `511` or `0o755`
+@param config.storagePath    {String}  - [Server] Storage path on file system
+@param config.cacheControl   {String}  - [Server] Default `Cache-Control` header
+@param config.throttle       {Number}  - [Server] bps throttle threshold
+@param config.downloadRoute  {String}  - [Both]   Server Route used to retrieve files
+@param config.collectionName {String}  - [Both]   Collection name
+@param config.namingFunction {Function}- [Both]   Function which returns `String`
+@param config.integrityCheck {Boolean} - [Server] Check file's integrity before serving to users
+@param config.onAfterUpload  {Function}- [Server] Called right after file is ready on FS. Use to transfer file somewhere else, or do other thing with file directly
+@param config.onBeforeUpload {Function}- [Both]   Function which executes on server after receiving each chunk and on client right before beginning upload. Function context is `File` - so you are able to check for extension, mime-type, size and etc.
 return `true` to continue
 return `false` or `String` to abort upload
-@param config.allowClientCode  {Boolean}  - Allow to run `remove` from client
-@param config.downloadCallback {Function} - Callback triggered each time file is requested, return truthy value to continue download, or falsy to abort
-@param config.onbeforeunloadMessage {String|Function} - Message shown to user when closing browser's window or tab while upload process is running
+@param config.allowClientCode  {Boolean}  - [Both]   Allow to run `remove` from client
+@param config.downloadCallback {Function} - [Server] Callback triggered each time file is requested, return truthy value to continue download, or falsy to abort
+@param config.onbeforeunloadMessage {String|Function} - [Client] Message shown to user when closing browser's window or tab while upload process is running
 @description Create new instance of Meteor.Files
 ###
 class Meteor.Files
   constructor: (config) ->
-    {@storagePath, @collectionName, @downloadRoute, @schema, @chunkSize, @namingFunction, @debug, @onbeforeunloadMessage, @permissions, @allowClientCode, @onBeforeUpload, @integrityCheck, @protected, @public, @strict, @downloadCallback, @cacheControl, @throttle} = config if config
+    {@storagePath, @collectionName, @downloadRoute, @schema, @chunkSize, @namingFunction, @debug, @onbeforeunloadMessage, @permissions, @allowClientCode, @onBeforeUpload, @integrityCheck, @protected, @public, @strict, @downloadCallback, @cacheControl, @throttle, @onAfterUpload} = config if config

     self               = @
+    cookie             = new Cookies()
     @debug            ?= false
     @public           ?= false
-    @strict           ?= true
     @protected        ?= false
     @chunkSize        ?= 1024*512
     @chunkSize         = Math.floor(@chunkSize / 8) * 8
-    @permissions      ?= 0o755
-    @cacheControl     ?= 'public, max-age=31536000, s-maxage=31536000'
+    @downloadRoute    ?= if @public then "/uploads/#{@collectionName}" else '/cdn/storage'
+    @downloadRoute     = @downloadRoute.replace /\/$/, ''
     @collectionName   ?= 'MeteorUploadFiles'
     @namingFunction   ?= -> Random.id()
-    @integrityCheck   ?= true
     @onBeforeUpload   ?= false
     @allowClientCode  ?= true
-    @downloadCallback ?= false
-    @onbeforeunloadMessage ?= 'Upload in a progress... Do you want to abort?'
-    @throttle         ?= false
-
-    cookie = new Cookies()
-    if @protected and Meteor.isClient
-      if not cookie.has('meteor_login_token') and Meteor._localStorage.getItem('Meteor.loginToken')
-        cookie.set 'meteor_login_token', Meteor._localStorage.getItem('Meteor.loginToken'), null, '/'
-
-    if not @storagePath
-      @storagePath   = if @public then "../web.browser/app/uploads/#{@collectionName}" else "assets/app/uploads/#{@collectionName}"
-      @downloadRoute = if @public then "/uploads/#{@collectionName}" else '/cdn/storage' if not @downloadRoute
-
-    if not @downloadRoute
-      @downloadRoute = '/cdn/storage'
-
+
+    if Meteor.isClient
+      @onbeforeunloadMessage ?= 'Upload in a progress... Do you want to abort?'
+      delete @strict
+      delete @throttle
+      delete @storagePath
+      delete @permissions
+      delete @cacheControl
+      delete @onAfterUpload
+      delete @integrityCheck
+      delete @downloadCallback
+      if @protected
+        if not cookie.has('meteor_login_token') and Meteor._localStorage.getItem('Meteor.loginToken')
+          cookie.set 'meteor_login_token', Meteor._localStorage.getItem('Meteor.loginToken'), null, '/'
+
+      check @onbeforeunloadMessage, Match.OneOf String, Function
+    else
+      @strict           ?= true
+      @throttle         ?= false
+      @permissions      ?= 0o755
+      @cacheControl     ?= 'public, max-age=31536000, s-maxage=31536000'
+      @onAfterUpload    ?= false
+      @integrityCheck   ?= true
+      @downloadCallback ?= false
+      @storagePath      ?= if @public then "../web.browser/app/uploads/#{@collectionName}" else "assets/app/uploads/#{@collectionName}"
+      @storagePath       = @storagePath.replace /\/$/, ''
+
+      check @strict, Boolean
+      check @throttle, Match.OneOf false, Number
+      check @permissions, Number
+      check @storagePath, String
+      check @cacheControl, String
+      check @onAfterUpload, Match.OneOf Boolean, Function
+      check @integrityCheck, Boolean
+      check @downloadCallback, Match.OneOf Boolean, Function

     if not @schema
       @schema =
@@ -163,30 +182,21 @@
     check @debug, Boolean
     check @schema, Object
     check @public, Boolean
-    check @strict, Boolean
-    check @throttle, Match.OneOf false, Number
     check @protected, Match.OneOf Boolean, Function
     check @chunkSize, Number
-    check @permissions, Number
-    check @storagePath, String
     check @downloadRoute, String
-    check @integrityCheck, Boolean
     check @collectionName, String
     check @namingFunction, Function
     check @onBeforeUpload, Match.OneOf Boolean, Function
     check @allowClientCode, Boolean
-    check @downloadCallback, Match.OneOf Boolean, Function
-    check @onbeforeunloadMessage, Match.OneOf String, Function

     if @public and @protected
-      throw new Meteor.Error 500, "[Meteor.File.#{@collectionName}]: Files can not be public and protected at the same time!"
+      throw new Meteor.Error 500, "[Meteor.Files.#{@collectionName}]: Files can not be public and protected at the same time!"

     @cursor        = null
     @search        = {}
     @collection    = new Mongo.Collection @collectionName
     @currentFile   = null
-    @storagePath   = @storagePath.replace /\/$/, ''
-    @downloadRoute = @downloadRoute.replace /\/$/, ''

     @collection.attachSchema @schema
     @collection.deny
@@ -194,7 +204,7 @@
       update: -> true
       remove: -> true

-    @_prefix = SHA256 @collectionName + @storagePath + @downloadRoute
+    @_prefix = SHA256 @collectionName + @downloadRoute
     _insts[@_prefix] = @

     @checkAccess = (http) ->
@@ -213,7 +223,7 @@
           return true
         else
           rc = if _.isNumber(result) then result else 401
-          console.warn 'Access denied!' if self.debug
+          console.warn '[Meteor.Files.checkAccess] WARN: Access denied!' if self.debug
           if http
             text = 'Access denied!'
             http.response.writeHead rc,
@@ -276,11 +286,11 @@
       _methods = {}
       _methods[self.methodNames.MeteorFileUnlink] = (inst) ->
         check inst, Object
-        console.info 'Meteor.Files Debugger: [MeteorFileUnlink]' if self.debug
+        console.info '[Meteor.Files] [Unlink Method]' if self.debug
         if self.allowClientCode
           self.remove.call cp(_insts[inst._prefix], inst), inst.search
         else
-          throw new Meteor.Error 401, '[Meteor.Files] [remove()] Run code from client is not allowed!'
+          throw new Meteor.Error 401, '[Meteor.Files] [remove] Run code from client is not allowed!'

       _methods[self.methodNames.MeteorFileWrite] = (opts) ->
         @unblock()
@@ -291,12 +301,10 @@
           binData: String
           chunkId: Number
           fileLength: Number
-          _binSize: Number
           eof: Boolean
         }

-        console.info "Meteor.Files Debugger: [MeteorFileWrite] {name: #{opts.fileId}, meta:#{opts.meta}}" if self.debug
-        console.info "Meteor.Files Debugger: Received chunk ##{opts.chunkId} of #{opts.fileLength} chunks, file: #{opts.file.name or opts.file.fileName}" if self.debug
+        console.info "[Meteor.Files] [Write Method] Got ##{opts.chunkId}/#{opts.fileLength} chunks, dst: #{opts.file.name or opts.file.fileName}" if self.debug

         if self.onBeforeUpload and _.isFunction self.onBeforeUpload
           isUploadAllowed = self.onBeforeUpload.call {file: opts.file}, opts.file
@@ -310,9 +318,9 @@
         path     = if self.public then "#{self.storagePath}/original-#{opts.fileId}#{extensionWithDot}" else "#{self.storagePath}/#{opts.fileId}#{extensionWithDot}"
         pathPart = if opts.fileLength > 1 then "#{pathName}_#{opts.chunkId}#{extensionWithDot}" else path

-        result = _.extend self.dataToSchema(_.extend(opts.file, {path, extension, name: fileName, meta: opts.meta})), {_id: opts.fileId, chunkId: opts.chunkId, _binSize: opts._binSize}
-
-        action = (cb) ->
+        result = _.extend self.dataToSchema(_.extend(opts.file, {path, extension, name: fileName, meta: opts.meta})), {_id: opts.fileId, chunkId: opts.chunkId}
+
+        action = (cb) -> Meteor.defer ->
           binary = new Buffer opts.binData, 'base64'
           tries  = 0

@@ -323,13 +331,13 @@
             if !!~findex
               files.splice(findex, 1)
             else
-              console.warn "finish as no more files", files, {sindex, findex}, "#{opts.fileId}_#{num}#{extensionWithDot}" if self.debug
+              console.warn "[Meteor.Files] [Write Method] [concatChunks] finish as no more file's chunks", files, {sindex, findex}, "#{opts.fileId}_#{num}#{extensionWithDot}" if self.debug
               return finish cb

             _path   = "#{pathName}_#{num}#{extensionWithDot}"
             _source = pathName + '_1' + extensionWithDot

-            fs.stat _path, (error, stats) ->
+            fs.stat _path, (error, stats) -> bound ->
               if error or not stats.isFile()
                 if tries >= 10
                   cb new Meteor.Error 500, "Chunk ##{num} is missing!"
@@ -339,11 +347,11 @@
                     concatChunks num, files, cb
                   , 100
               else
-                fs.readFile _path, (error, _chunkData) ->
+                fs.readFile _path, (error, _chunkData) -> bound ->
                   if error
                     cb new Meteor.Error 500, "Can't read #{_path}"
                   else
-                    fs.appendFile _source, _chunkData, (error) ->
+                    fs.appendFile _source, _chunkData, (error) -> bound ->
                       if error
                         cb new Meteor.Error 500, "Can't append #{_path} to #{_source}"
                       else
@@ -366,12 +374,13 @@
                 cb new Meteor.Error 500, error
               else
                 result._id = _id
-                console.info "Meteor.Files Debugger: The file #{fileName} (binary) was saved to #{path}" if self.debug
+                console.info "[Meteor.Files] [Write Method] [finish] #{fileName} -> #{path}" if self.debug
+                self.onAfterUpload and self.onAfterUpload null, result
                 cb null, result
           try
             if opts.eof
               if opts.fileLength > 1
-                fs.readdir self.storagePath, (error, files) ->
+                fs.readdir self.storagePath, (error, files) -> bound ->
                   if error
                     cb new Meteor.Error 500, error
                   else
@@ -379,16 +388,16 @@
               else
                 finish cb
             else
-              fs.outputFile pathPart, binary, 'binary', (error) ->
-                cb error, result
+              fs.outputFile pathPart, binary, 'binary', (error) -> bound -> cb error, result
           catch e
             cb e
+          return

         if opts.eof
           try
             return Meteor.wrapAsync(action)()
           catch e
-            console.warn "Meteor.Files Debugger: Insert (Upload) Exception:", e if self.debug
+            console.warn "[Meteor.Files] [Write Method] Exception:", e if self.debug
             throw e
         else
           action NOOP
@@ -404,7 +413,7 @@
         ext  = ".#{opts.fileData.ext}"
         path = if self.public then "#{self.storagePath}/original-#{opts.fileId}" else "#{self.storagePath}/#{opts.fileId}"

-        console.info "Meteor.Files Debugger: Abort for #{path}" if self.debug
+        console.info "[Meteor.Files] [Abort Method]: For #{path}" if self.debug
         if opts.fileLength > 1
           i = 0
           while i <= opts.fileLength
@@ -414,6 +423,9 @@
                 fs.unlink @_path, NOOP
             ).bind({_path})
             i++
+        Meteor.setTimeout ->
+          self.remove opts.fileId
+        , 250

       Meteor.methods _methods

@@ -561,7 +573,7 @@
   @returns {Files} - Returns current Meteor.Files instance
   ###
   write: if Meteor.isServer then (buffer, opts = {}, callback) ->
-    console.info "Meteor.Files Debugger: [write(buffer, #{JSON.stringify(opts)}, callback)]" if @debug
+    console.info "[Meteor.Files] [write()]" if @debug
     check opts, Match.Optional Object
     check callback, Match.Optional Function

@@ -585,7 +597,7 @@
         size:      opts.size
         extension: extension

-      console.info "Meteor.Files Debugger: The file #{fileName} (binary) was added to #{@collectionName}" if @debug
+      console.info "[Meteor.Files] [write]: #{fileName} -> #{@collectionName}" if @debug

       fs.outputFile path, buffer, 'binary', (error) -> bound ->
         if error
@@ -610,7 +622,7 @@
   @returns {Files} - Return this
   ###
   load: if Meteor.isServer then (url, opts = {}, callback) ->
-    console.info "Meteor.Files Debugger: [load(#{url}, #{JSON.stringify(opts)}, callback)]" if @debug
+    console.info "[Meteor.Files] [load(#{url}, #{JSON.stringify(opts)}, callback)]" if @debug
     check url, String
     check opts, Match.Optional Object
     check callback, Match.Optional Function
@@ -624,10 +636,10 @@
     opts.meta = {} if not opts.meta

     request.get(url).on('error', (error)-> bound ->
-      throw new Meteor.Error 500, "Error on [load(#{url}, #{opts})]; Error:" + JSON.stringify error
+      throw new Meteor.Error 500, "Error on [load(#{url})]:" + JSON.stringify error
     ).on('response', (response) -> bound ->

-      console.info "Meteor.Files Debugger: The file #{url} is received" if self.debug
+      console.info "[Meteor.Files] [load] Received: #{url}" if self.debug

       result = self.dataToSchema
         name:      fileName
@@ -639,10 +651,10 @@

       self.collection.insert _.clone(result), (error, fileRef) ->
         if error
-          console.warn "Meteor.Files Debugger: Can't add file #{fileName} (binary) to #{self.collectionName}" if self.debug
+          console.warn "[Meteor.Files] [load] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug
           callback and callback error
         else
-          console.info "Meteor.Files Debugger: The file #{fileName} (binary) was added to #{self.collectionName}" if self.debug
+          console.info "[Meteor.Files] [load] [insert] #{fileName} -> #{self.collectionName}" if self.debug
           callback and callback null, fileRef

     ).pipe fs.createOutputStream path
@@ -662,9 +674,9 @@
   @returns {Files} - Return this
   ###
   addFile: if Meteor.isServer then (path, opts = {}, callback) ->
-    console.info "[addFile(#{path})]" if @debug
-
-    throw new Meteor.Error 403, 'Can not run [addFile()] on public collection' if @public
+    console.info "[Meteor.Files] [addFile(#{path})]" if @debug
+
+    throw new Meteor.Error 403, 'Can not run [addFile] on public collection' if @public
     check path, String
     check opts, Match.Optional Object
     check callback, Match.Optional Function
@@ -697,13 +709,13 @@
         _cn = self.collectionName
         self.collection.insert _.clone(result), (error, record) ->
           if error
-            console.warn "Can't add file #{fileName} (binary) to #{_cn}" if self.debug
+            console.warn "[Meteor.Files] [addFile] [insert] Error: #{fileName} -> #{_cn}", error if self.debug
             callback and callback error
           else
-            console.info "The file #{fileName} (binary) was added to #{_cn}" if self.debug
+            console.info "[Meteor.Files] [addFile] [insert]: #{fileName} -> #{_cn}" if self.debug
             callback and callback null, result
       else
-        callback and callback new Meteor.Error 400, "[Files.addFile(#{path})]: File does not exist"
+        callback and callback new Meteor.Error 400, "[Meteor.Files] [addFile(#{path})]: File does not exist"

     return @
   else
@@ -719,7 +731,7 @@
   @returns {Files} - Return this
   ###
   findOne: (search) ->
-    console.info "Meteor.Files Debugger: [findOne(#{JSON.stringify(search)})]" if @debug
+    console.info "[Meteor.Files] [findOne(#{JSON.stringify(search)})]" if @debug
     check search, Match.Optional Match.OneOf Object, String
     @srch search

@@ -738,7 +750,7 @@
   @returns {Files} - Return this
   ###
   find: (search) ->
-    console.info "Meteor.Files Debugger: [find(#{JSON.stringify(search)})]" if @debug
+    console.info "[Meteor.Files] [find(#{JSON.stringify(search)})]" if @debug
     check search, Match.Optional Match.OneOf Object, String
     @srch search

@@ -756,7 +768,7 @@
   @returns {Object|[Object]}
   ###
   get: () ->
-    console.info 'Meteor.Files Debugger: [get()]' if @debug
+    console.info '[Meteor.Files] [get()]' if @debug
     return @cursor.fetch() if @cursor
     return @currentFile

@@ -769,7 +781,7 @@
   @returns {[Object]}
   ###
   fetch: () ->
-    console.info 'Meteor.Files Debugger: [fetch()]' if @debug
+    console.info '[Meteor.Files] [fetch()]' if @debug
     data = @get()
     if not _.isArray data
       return [data]
@@ -784,6 +796,7 @@
   @param {Object} config - Configuration object with next properties:
     {File|Object} file           - HTML5 `files` item, like in change event: `e.currentTarget.files[0]`
     {Object}      meta           - Additional data as object, use later for search
+    {Boolean}     allowWebWorkers- Allow/Deny WebWorkers usage
     {Number|dynamic} streams     - Quantity of parallel upload streams, default: 2
     {Number|dynamic} chunkSize   - Chunk size for upload
     {Function}    onUploaded     - Callback triggered when upload is finished, with two arguments `error` and `fileRef`
@@ -807,35 +820,42 @@
   ###
   insert: if Meteor.isClient then (config) ->
     if @checkAccess()
-      console.info 'Meteor.Files Debugger: [insert()]' if @debug
-      {file, meta, onUploaded, onProgress, onBeforeUpload, onAbort, streams, onError, chunkSize, onReady, FileReadProgress} = config
-      meta      ?= {}
-      streams   ?= 2
-      chunkSize ?= this.chunkSize
-
-      check meta, Match.Optional Object
-      check onAbort, Match.Optional Function
-      check streams, Match.OneOf 'dynamic', Number
-      check chunkSize, Match.OneOf 'dynamic', Number
-      check onUploaded, Match.Optional Function
-      check onProgress, Match.Optional Function
-      check onBeforeUpload, Match.Optional Function
-      check onError, Match.Optional Function
-      check onReady, Match.Optional Function
-      check FileReadProgress, Match.Optional Function
-
-      if file
+      console.info '[Meteor.Files] [insert()]' if @debug
+      config.meta      ?= {}
+      config.streams   ?= 2
+      config.streams    = 2 if config.streams < 1
+      config.chunkSize ?= @chunkSize
+      config.allowWebWorkers ?= true
+
+      check config, {
+        file: Match.Any
+        meta: Match.Optional Object
+        onAbort: Match.Optional Function
+        streams: Match.OneOf 'dynamic', Number
+        chunkSize: Match.OneOf 'dynamic', Number
+        onUploaded: Match.Optional Function
+        onProgress: Match.Optional Function
+        onBeforeUpload: Match.Optional Function
+        onError: Match.Optional Function
+        allowWebWorkers: Boolean
+      }
+
+      if config.file
+        if URL and Blob and Worker and config.allowWebWorkers
+          worker   = new Worker URL.createObjectURL new Blob ['self.onmessage=function(t){var e=new FileReader;e.onloadend=function(a){var n,r;postMessage({bin:((null!=e?e.result:void 0)||(null!=(n=a.srcElement)?n.result:void 0)||(null!=(r=a.target)?r.result:void 0)).split(",")[1],chunkId:t.data.currentChunk,start:t.data.start})},e.onerror=function(t){throw(t.target||t.srcElement).error},e.readAsDataURL(t.data.file.slice(t.data.chunkSize*(t.data.currentChunk-1),t.data.chunkSize*t.data.currentChunk))};']
+        else
+          worker   = null
         console.time('insert') if @debug
+        console.time('loadFile') if @debug
         self       = @
-        fileReader = new FileReader
         fileLength = 1
         fileId     = @namingFunction()
         fileData   =
-          size: file.size
-          type: file.type
-          name: file.name
-
-        fileData = _.extend fileData, @getExt(file.name), {mime: @getMimeType(fileData)}
+          size: config.file.size
+          type: config.file.type
+          name: config.file.name
+
+        fileData = _.extend fileData, @getExt(config.file.name), {mime: @getMimeType(fileData)}
         fileData['mime-type'] = fileData.mime

         beforeunload = (e) ->
@@ -845,56 +865,67 @@
         window.addEventListener 'beforeunload', beforeunload, false

         result  =
-          file: _.extend file, fileData
-          onPause: new ReactiveVar false
-          continueFunc: -> return
+          file:          _.extend config.file, fileData
+          state:         new ReactiveVar 'active'
+          onPause:       new ReactiveVar false
+          progress:      new ReactiveVar 0
+          estimateTime:  new ReactiveVar 1000
+          estimateSpeed: new ReactiveVar 0
+          continueFunc:  -> return
           pause: ->
-            @onPause.set true
-            @state.set 'paused'
+            unless @onPause.get()
+              @onPause.set true
+              @state.set 'paused'
+            return
           continue: ->
-            @onPause.set false
-            @state.set 'active'
-            @continueFunc.call()
-            @continueFunc = -> return
+            if @onPause.get()
+              @onPause.set false
+              @state.set 'active'
+              @continueFunc.call()
+              @continueFunc = -> return
+            return
           toggle: ->
             if @onPause.get() then @continue() else @pause()
-          progress: new ReactiveVar 0
+            return
           abort: ->
             window.removeEventListener 'beforeunload', beforeunload, false
-            onAbort and onAbort.call @, fileData
-            fileReader.abort()
+            config.onAbort and config.onAbort.call @, fileData
             @pause()
             @state.set 'aborted'
+            console.timeEnd('insert') if self.debug
             Meteor.call self.methodNames.MeteorFileAbort, {fileId, fileLength, fileData}
+            worker.terminate() if worker
             delete upload
-          state: new ReactiveVar 'active'
-          readAsDataURL: -> fileReader?.result
-
-        result.progress.set = _.throttle result.progress.set, 250
+            return

         Tracker.autorun ->
-          if Meteor.status().connected
-            result.continue()
-            console.info 'Meteor.Files Debugger: Connection established continue() upload' if self.debug
-          else
-            result.pause()
-            console.info 'Meteor.Files Debugger: Connection error set upload on pause()' if self.debug
+          unless result.onPause.get()
+            if Meteor.status().connected
+              result.continue()
+              console.info '[Meteor.Files] [insert] [Tracker] [continue]' if self.debug
+            else
+              result.pause()
+              console.info '[Meteor.Files] [insert] [Tracker] [pause]' if self.debug
+          return

         end = (error, data) ->
           console.timeEnd('insert') if self.debug
           window.removeEventListener 'beforeunload', beforeunload, false
           result.progress.set 0
-          onUploaded and onUploaded.call result, error, data
+          config.onUploaded and config.onUploaded.call result, error, data
           if error
+            console.warn "[Meteor.Files] [insert] [end] Error: ", error if self.debug
             result.state.set 'aborted'
-            onError and onError.call result, error, fileData
+            config.onError and config.onError.call result, error, fileData
           else
             result.state.set 'completed'
-
-        if onBeforeUpload and _.isFunction onBeforeUpload
-          isUploadAllowed = onBeforeUpload.call result, fileData
+          worker.terminate() if worker
+          return
+
+        if config.onBeforeUpload and _.isFunction config.onBeforeUpload
+          isUploadAllowed = config.onBeforeUpload.call result, fileData
           if isUploadAllowed isnt true
-            end new Meteor.Error(403, if _.isString(isUploadAllowed) then isUploadAllowed else 'Files.onBeforeUpload() returned false'), null
+            end new Meteor.Error(403, if _.isString(isUploadAllowed) then isUploadAllowed else 'config.onBeforeUpload() returned false'), null
             return false

         if @onBeforeUpload and _.isFunction @onBeforeUpload
@@ -905,97 +936,134 @@

         currentChunk = 0
         sentChunks   = 0
-        binary   = ''
-        _binSize = 0
-        EOFsent  = false
-
-        sendEOF = (opts) ->
-          unless EOFsent
-            EOFsent = true
-            Meteor.setTimeout ->
-              opts.binData  = 'EOF'
-              opts.eof      = true
-              opts.chunkId  = -1
-              opts._binSize = -1
-              Meteor.call self.methodNames.MeteorFileWrite, opts, end
-            , 50
-
-        upload = (fileLength) ->
-          opts =
-            meta: meta
+        EOFsent      = false
+        transferTime = 0
+
+        calculateStats = _.throttle ->
+          _t = (transferTime / sentChunks) / config.streams
+          result.estimateTime.set (_t * (fileLength - sentChunks))
+          result.estimateSpeed.set (config.chunkSize / (_t / 1000))
+          progress = Math.round((sentChunks / fileLength) * 100)
+          result.progress.set progress
+          config.onProgress and config.onProgress.call result, progress
+          return
+        , 250
+
+        sendViaDDP = (evt) -> Meteor.defer ->
+          console.timeEnd('loadFile') if self.debug
+          opts =
+            meta: config.meta
             file: fileData
             fileId: fileId
+            binData: evt.data.bin
+            chunkId: evt.data.chunkId
             fileLength: fileLength
             eof: false

-          if result.onPause.get()
-            result.continueFunc = -> upload fileLength
-            return
-
-          if _binSize > 0
-            opts.chunkId = ++currentChunk
-            opts.binData = binary.substring 0, chunkSize
-            binary       = binary.substring chunkSize
-            _binSize     = opts._binSize = _.clone binary.length
-
-            Meteor.call self.methodNames.MeteorFileWrite, opts, (error, data) ->
+          if opts.binData and opts.binData.length
+            Meteor.call self.methodNames.MeteorFileWrite, opts, (error) ->
               ++sentChunks
+              transferTime += (+new Date) - evt.data.start
               if error
                 end error
               else
-                progress = (data.chunkId / fileLength) * 100
-                result.progress.set Math.ceil(progress)
-                onProgress and onProgress.call result, progress
-
-                unless result.onPause.get()
-                  if data._binSize <= 0
-                    sendEOF opts
-                  else
-                    upload fileLength
-                else
-                  result.continueFunc = -> upload fileLength
+                if sentChunks >= fileLength
+                  sendEOF opts
+                else if currentChunk < fileLength
+                  upload()
+                calculateStats()
+              return
           else
             sendEOF opts
-
-        createStreams = (fileLength) ->
+          return
+
+        sendEOF = (opts) -> Meteor.defer ->
+          unless EOFsent
+            EOFsent = true
+            opts =
+              meta: config.meta
+              file: fileData
+              fileId: fileId
+              binData: 'EOF'
+              chunkId: -1
+              fileLength: fileLength
+              eof: true
+            Meteor.call self.methodNames.MeteorFileWrite, opts, end
+          return
+
+        if worker
+          worker.onmessage = sendViaDDP
+          worker.onerror   = (e) ->
+            end e.message
+            return
+
+        if self.debug
+          if worker
+            console.info "[Meteor.Files] [insert] using WebWorkers"
+          else
+            console.info "[Meteor.Files] [insert] using MainThread"
+
+        upload = -> Meteor.defer ->
+          start = +new Date
+          if result.onPause.get()
+            result.continueFunc = -> createStreams()
+            return
+
+          ++currentChunk
+          if worker
+            worker.postMessage({sentChunks, start, currentChunk, chunkSize: config.chunkSize, file: config.file})
+          else
+            ((chunkId) ->
+              fileReader = new FileReader
+              chunk = config.file.slice (config.chunkSize * (chunkId - 1)), (config.chunkSize * chunkId)
+              readHandler = (evt) ->
+                sendViaDDP data:
+                  bin: (fileReader?.result or evt.srcElement?.result or evt.target?.result).split(',')[1]
+                  chunkId: chunkId
+                  start: start
+                return
+
+              fileReader.onloadend = readHandler
+              fileReader.onerror = (e) ->
+                result.abort()
+                onError and onError.call result, (e.target or e.srcElement).error, fileData
+                return
+
+              fileReader.readAsDataURL chunk
+              return
+            )(currentChunk)
+          return
+
+        createStreams = ->
           i = 1
-          while i <= streams
-            Meteor.defer -> upload fileLength
+          while i <= config.streams
+            upload()
             i++
           return

-        readHandler = (chunk) ->
-          binary    = (fileReader?.result or chunk.srcElement?.result or chunk.target?.result).split(',')[1]
-          if binary and binary.length
-            onReady and onReady.call result, fileData
-            binSize   = _.clone binary.length
-            _binSize  = _.clone binary.length
-            if chunkSize is 'dynamic'
-              if binSize >= 2048 * streams
-                chunkSize = Math.ceil binSize / (8 * streams)
-              else
-                chunkSize = self.chunkSize
-            if streams is 'dynamic'
-              streams = Math.ceil binSize / chunkSize
-              if streams > 32
-                streams = 32
-            chunkSize = Math.floor(chunkSize / 8) * 8
-            _len = Math.ceil(binSize / chunkSize)
-            fileLength = if _len <= 0 then 1 else _len
-            if streams > fileLength
-              streams = fileLength
-            createStreams fileLength
-
-        if FileReadProgress
-          fileReader.onprogress = (e) -> FileReadProgress.call result, ((e.loaded / file.size) * 100)
-        fileReader.onloadend = readHandler
-        fileReader.onerror = (e) ->
-          result.abort()
-          error = (e.target or e.srcElement).error
-          onError and onError.call result, error, fileData
-
-        Meteor.defer -> fileReader.readAsDataURL file
+        prepare = ->
+          if config.chunkSize is 'dynamic'
+            if config.file.size >= 104857600
+              config.chunkSize = 1048576
+            else if config.file.size >= 52428800
+              config.chunkSize = 524288
+            else
+              config.chunkSize = 262144
+
+          _len = Math.ceil(config.file.size / config.chunkSize)
+          if config.streams is 'dynamic'
+            config.streams = _.clone _len
+            config.streams = 32 if config.streams > 32
+
+          fileLength     = if _len <= 0 then 1 else _len
+          config.streams = fileLength if config.streams > fileLength
+          console.log {config, fileLength}
+          createStreams()
+          return
+        prepare()
         return result
+      else
+        console.warn "[Meteor.Files] [insert] Have you forget to pass a File itself?"
   else
     undefined

@@ -1009,7 +1077,7 @@
   @returns {undefined}
   ###
   remove: (search) ->
-    console.info "Meteor.Files Debugger: [remove(#{JSON.stringify(search)})]" if @debug
+    console.info "[Meteor.Files] [remove(#{JSON.stringify(search)})]" if @debug
     check search, Match.Optional Match.OneOf Object, String

     if @checkAccess()
@@ -1044,7 +1112,7 @@

   _404: if Meteor.isServer then (http) ->
-    console.warn "Meteor.Files Debugger: [download(#{http.request.originalUrl})] [404] File not found" if @debug
+    console.warn "[Meteor.Files] [download(#{http.request.originalUrl})] [_404] File not found" if @debug
     text = 'File Not Found :('
     http.response.writeHead 404,
       'Content-Length': text.length
@@ -1062,7 +1130,7 @@
   @returns {undefined}
   ###
   download: if Meteor.isServer then (http, version = 'original') ->
-    console.info "Meteor.Files Debugger: [download(#{http.request.originalUrl}, #{version})]" if @debug
+    console.info "[Meteor.Files] [download(#{http.request.originalUrl}, #{version})]" if @debug
     responseType = '200'
     if not @public
       if @currentFile
@@ -1151,7 +1219,7 @@

         switch responseType
           when '400'
-            console.warn "Meteor.Files Debugger: [download(#{fileRef.path}, #{version})] [400] Content-Length mismatch!" if self.debug
+            console.warn "[Meteor.Files] [download(#{fileRef.path}, #{version})] [400] Content-Length mismatch!" if self.debug
             text = 'Content-Length mismatch!'
             http.response.writeHead 400,
               'Content-Type':   'text/plain'
@@ -1163,13 +1231,13 @@
             return self._404 http
             break
           when '416'
-            console.info "Meteor.Files Debugger: [download(#{fileRef.path}, #{version})] [416] Content-Range is not specified!" if self.debug
+            console.info "[Meteor.Files] [download(#{fileRef.path}, #{version})] [416] Content-Range is not specified!" if self.debug
             http.response.writeHead 416,
               'Content-Range': "bytes */#{fileRef.size}"
             http.response.end()
             break
           when '200'
-            console.info "Meteor.Files Debugger: [download(#{fileRef.path}, #{version})] [200]" if self.debug
+            console.info "[Meteor.Files] [download(#{fileRef.path}, #{version})] [200]" if self.debug
             stream = fs.createReadStream fileRef.path
             stream.on('open', =>
               http.response.writeHead 200
@@ -1181,7 +1249,7 @@
             ).on 'error', streamErrorHandler
             break
           when '206'
-            console.info "Meteor.Files Debugger: [download(#{fileRef.path}, #{version})] [206]" if self.debug
+            console.info "[Meteor.Files] [download(#{fileRef.path}, #{version})] [206]" if self.debug
             http.response.setHeader 'Content-Range', "bytes #{reqRange.start}-#{reqRange.end}/#{fileRef.size}"
             http.response.setHeader 'Trailer', 'expires'
             http.response.setHeader 'Transfer-Encoding', 'chunked'
@@ -1215,7 +1283,7 @@
   @returns {String} Empty string returned in case if file not found in DB
   ###
   link: (fileRef, version = 'original', pub = false) ->
-    console.info 'Meteor.Files Debugger: [link()]' if @debug
+    console.info '[Meteor.Files] [link()]' if @debug
     if _.isString fileRef
       version = fileRef
       fileRef = null


Rename user.person to user.person.id

Rename user.person to user.person.id

--- server/profile.coffee
+++ server/profile.coffee
@@ -14,7 +14,8 @@
       person.publications = options.profile.publications if options.profile.publications?

     personId = Persons.insert person
-    user.person = personId
+    user.person =
+      id: personId
   catch e
     if e.name isnt 'MongoError'
       throw e


Code styling.

Code styling.

--- client/Main/addworkspaceview.coffee
+++ client/Main/addworkspaceview.coffee
@@ -18,6 +18,7 @@

   handleKeyDown: (event) ->
+
     if event.which is 13
       options      =
         name       : @input.getValue()


Re-enabled global ajax error messages.

Re-enabled global ajax error messages.

--- app/assets/javascripts/app/lib/app_post/ajax.js.coffee
+++ app/assets/javascripts/app/lib/app_post/ajax.js.coffee
@@ -2,11 +2,11 @@
   _instance = undefined # Must be declared here to force the closure on the class
   @ajax: (args) -> # Must be a static method
     if _instance == undefined
-      _instance ?= new _Singleton
+      _instance ?= new _ajaxSingleton
     _instance.ajax(args)

 # The actual Singleton class
-class _Singleton
+class _ajaxSingleton
   defaults:
     contentType: 'application/json'
     dataType: 'json'
@@ -33,7 +33,7 @@
     )

     # show error messages
-    $('body').bind( 'ajaxError', ( e, jqxhr, settings, exception ) ->
+    $(document).bind( 'ajaxError', ( e, jqxhr, settings, exception ) ->
       status = jqxhr.status
       detail = jqxhr.responseText
       if !status && !detail
@@ -44,6 +44,9 @@

       # do not show any error message on wrong login
       return if status is 422
+
+      # do not show any error message with code 200
+      return if status is 200

       # show human readable message
       if status is 401


Stop redirecting to movies index when adding a movie

--- app/assets/javascripts/routers/movies_router.js.coffee
+++ app/assets/javascripts/routers/movies_router.js.coffee
@@ -2,7 +2,6 @@
   initialize: (options) ->
     @movies = new Shothere.Collections.MoviesCollection()
     @movies.reset options.movies if options.movies
-    @movies.on( 'add', @index, @)

   routes:
     "new"      : "newMovie"


clean unused params on listIncidents

clean unused params on listIncidents

--- lib/pagerv2.coffee
+++ lib/pagerv2.coffee
@@ -295,7 +295,6 @@
     else
       query = {
         time_zone: 'UTC',
-        'include[]': 'first_trigger_log_entry',
         'urgencies[]': 'high',
         sort_by: 'created_at'
       }
@@ -310,7 +309,6 @@
         query['statuses[]'] = statuses.split /,/
       query['limit'] = limit
       query['total'] = 'true'
-      console.log query
       @request('GET', '/incidents', query)
       .then (data) ->
         data


More debug output

More debug output

--- server/models/submit.coffee
+++ server/models/submit.coffee
@@ -56,7 +56,9 @@

 submitsSchema.methods.equivalent = (other) ->
     if @user == "394891"
-        logger.log "Compare submits ", @_id, other._id, @comments.length > 0, @outcome == "AC" or @outcome == "IG" or @outcome == "DQ", @force, @user == other.user, @problem == other.problem, outcomeType(@outcome) == outcomeType(other.outcome), @source == other.source, Math.abs(@time - other.time)
+        logger.log "Compare submits ", @_id, other._id, @comments.length > 0, @outcome == "AC" or @outcome == "IG" or @outcome == "DQ", @force, @user == other.user, @problem == other.problem, outcomeType(@outcome) == outcomeType(other.outcome), @source.replace("\r", "") == other.source.replace("\r", ""), Math.abs(@time - other.time)
+        logger.log @source.replace("\r", "")
+        logger.log other.source.replace("\r", "")
     if @comments.length > 0
         return false
     if @outcome == "AC" or @outcome == "IG" or @outcome == "DQ"


add caching and key options to ms bing search

--- server/use/microsoft.coffee
+++ server/use/microsoft.coffee
@@ -66,14 +66,21 @@
 # https://docs.microsoft.com/en-gb/rest/api/cognitiveservices/bing-web-api-v7-reference#endpoints
 # annoyingly Bing search API does not provide exactly the same results as the actual Bing UI.
 # and it seems the bing UI is sometimes more accurate
-API.use.microsoft.bing.search = (q) ->
-  url = 'https://api.cognitive.microsoft.com/bing/v7.0/search?mkt=en-GB&count=20&q=' + q
-  API.log 'Using microsoft bing for ' + url
-  try
-    res = HTTP.call 'GET', url, {timeout: 10000, headers: {'Ocp-Apim-Subscription-Key': API.settings.use.microsoft.bing.key}}
-    if res.statusCode is 200 and res.data.webPages?.value
-      return {total: res.data.webPages.totalEstimatedMatches, data: res.data.webPages.value}
-    else
-      return { status: 'error', data: res.data}
-  catch err
-    return { status: 'error', data: 'error', error: err}
+API.use.microsoft.bing.search = (q, cache=false, refresh, key=API.settings.use.microsoft.bing.key) ->
+  if cache and cached = API.http.cache q, 'bing_search', undefined, refresh
+    cached.cache = true
+    return cached
+  else
+    url = 'https://api.cognitive.microsoft.com/bing/v7.0/search?mkt=en-GB&count=20&q=' + q
+    API.log 'Using microsoft bing for ' + url
+    try
+      res = HTTP.call 'GET', url, {timeout: 10000, headers: {'Ocp-Apim-Subscription-Key': key}}
+      if res.statusCode is 200 and res.data.webPages?.value
+        ret = {total: res.data.webPages.totalEstimatedMatches, data: res.data.webPages.value}
+        API.http.cache(pmcid, 'bing_search', ret) if cache and ret.total
+        return ret
+      else
+        return { status: 'error', data: res.data}
+    catch err
+      return { status: 'error', data: 'error', error: err}
+


Remove unused require

Remove unused require

--- apps/artwork_2/components/related_artworks/view.coffee
+++ apps/artwork_2/components/related_artworks/view.coffee
@@ -1,5 +1,4 @@
 Backbone = require 'backbone'
-{ CLIENT } = require('sharify').data
 metaphysics = require '../../../../lib/metaphysics.coffee'
 ArtworkMasonryView = require '../../../../components/artwork_masonry/view.coffee'
 helpers = require './helpers.coffee'


in anticipation of maybe being able to declare routes in the same actions linking file

--- shared/src/api/index.coffee
+++ shared/src/api/index.coffee
@@ -91,14 +91,14 @@
   topic: '*'
   action: '*'

-class APIHandler
-  constructor: (options, routes, channel) ->
+
+
+class APIHandlerBase
+  constructor: (options, channel) ->
     @setOptions(options)

-    @initializeRoutes(routes)
     @initializeRecords()
-    @initializeEventOptions(@getOptions().eventOptions, @getOptions().events)
-    @initializeEvents(@getOptions().events, routes.length, channel)
+    @initializeEventOptions(@getOptions().eventOptions)
     @initializeXHR(@getOptions().xhr, new Interceptors(@getOptions().hooks, @), @getOptions().isLocal)

   destroy: =>
@@ -106,9 +106,6 @@

   initializeRecords: =>
     @records = new XHRRecords()
-
-  initializeRoutes: (routes) =>
-    @routes = new Routes(routes)

   initializeXHR: (xhrOptions, interceptors, isLocal) =>
     xhr = axios.create(xhrOptions)
@@ -121,7 +118,7 @@
     #   will override existing headers, not merge recursively.
     _.assign(@_xhr.defaults, xhrOptions)

-  initializeEventOptions: (eventOptions, events) =>
+  initializeEventOptions: (eventOptions) =>
     {pattern, send, receive, statuses} = eventOptions

     # {subject}.{topic}.{action}.send||(receive.(failure||success))
@@ -130,9 +127,57 @@
       send: [pattern, send].join('.')
       receive: [pattern, receive].join('.')

-    sendRequest = @sendRequest
     @_patterns = patterns
     @_statuses = statuses
+
+  setOptions: (options) =>
+    previousOptions = @getOptions?() or {}
+    options = _.merge({}, API_DEFAULTS, previousOptions, options)
+
+    @getOptions = -> options
+
+  # send can now be used separately without initializing routes
+  send: (requestConfig, routeOptions, args...) ->
+    return if @records.isPending(requestConfig)
+
+    requestInfo = _.pick(routeOptions, 'subject', 'topic', 'action')
+    requestInfo = guessInfoFromConfig(requestConfig) if _.isEmpty(requestInfo)
+
+    requestConfig.events =
+      success: interpolate(@_patterns.receive, _.merge({status: @_statuses.success}, requestInfo))
+      failure: interpolate(@_patterns.receive, _.merge({status: @_statuses.failure}, requestInfo))
+
+    requestDelay = routeOptions.delay or @getOptions().request.delay
+
+    _.delay =>
+      {handlers} = @getOptions()
+      {onSuccess, onFail} = routeOptions
+
+      onSuccess ?= DEFAULT_SUCCESS
+      onFail ?= DEFAULT_FAIL
+
+      @_xhr.request(requestConfig)
+        # if onFail doesn't Promise.reject anything, then the default fail will not fire.
+        .then(_.partial(onSuccess, _, args...), _.partial(onFail, _, args...))
+        .then(_.partial(handlers.onSuccess, _, args...), _.partial(handlers.onFail, _, args...))
+
+    , requestDelay
+
+
+class APIHandler extends APIHandlerBase
+  constructor: (options, routes, channel) ->
+    super(options, channel)
+
+    @initializeRoutes(routes)
+    @setUpReceivers(@getOptions().events)
+    @initializeEvents(@getOptions().events, routes.length, channel)
+
+  initializeRoutes: (routes) =>
+    @routes = new Routes(routes)
+
+  setUpReceivers: (events) =>
+    sendRequest = @sendRequest
+    patterns = @_patterns

     handleSend = (args...) ->
       requestInfo = extractValues(@event, patterns.send)
@@ -143,12 +188,6 @@
   initializeEvents: (events, numberOfRoutes, channel) =>
     @channel = channel or new EventEmitter2 wildcard: true, maxListeners: numberOfRoutes * 2
     _.forEach(events, _.spread(@channel.on.bind(@channel)))
-
-  setOptions: (options) =>
-    previousOptions = @getOptions?() or {}
-    options = _.merge({}, API_DEFAULTS, previousOptions, options)
-
-    @getOptions = -> options

   sendRequest: (requestInfo, routeData, requestData, args...) =>
     routeOptions = @routes.get(requestInfo)
@@ -162,33 +201,6 @@

     @send(requestConfig, routeOptions, args...)

-  # send can now be used separately without initializing routes
-  send: (requestConfig, routeOptions, args...) ->
-    return if @records.isPending(requestConfig)
-
-    requestInfo = _.pick(routeOptions, 'subject', 'topic', 'action')
-    requestInfo = guessInfoFromConfig(requestConfig) if _.isEmpty(requestInfo)
-
-    requestConfig.events =
-      success: interpolate(@_patterns.receive, _.merge({status: @_statuses.success}, requestInfo))
-      failure: interpolate(@_patterns.receive, _.merge({status: @_statuses.failure}, requestInfo))
-
-    requestDelay = routeOptions.delay or @getOptions().request.delay
-
-    _.delay =>
-      {handlers} = @getOptions()
-      {onSuccess, onFail} = routeOptions
-
-      onSuccess ?= DEFAULT_SUCCESS
-      onFail ?= DEFAULT_FAIL
-
-      @_xhr.request(requestConfig)
-        # if onFail doesn't Promise.reject anything, then the default fail will not fire.
-        .then(_.partial(onSuccess, _, args...), _.partial(onFail, _, args...))
-        .then(_.partial(handlers.onSuccess, _, args...), _.partial(handlers.onFail, _, args...))
-
-    , requestDelay
-
 # include and export cascading error handler for convenience/custom error handling.

 module.exports = {APIHandler}


Fix bug when determining NextLevel in GPN

--- lib/session.coffee
+++ lib/session.coffee
@@ -528,9 +528,8 @@

       nextLevel = true
       if pair[0].length >= GET_PARAMETER_NAMES_DEPTH_THRESHOLD
-        nextLevel = false
-      else if common.hammingWeight(pair[1] >> 3) >= 3
-        nextLevel = false
+        if common.hammingWeight(pair[1] >> 3) >= 3
+          nextLevel = false

       if syncState.refreshGpn.size > 1 or (pair[1] > 1 and nextLevel)
         completed = false


put import in the right place

put import in the right place

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -57,6 +57,24 @@
     if symbolLocation and selection
       atom.workspace.open(symbolLocation)

+  addImportStatement: (importStatement) ->
+      editor = atom.workspace.getActiveTextEditor()
+      currentText = editor.getText()
+      currentPosition = editor.getCursorBufferPosition()
+      importMatches = currentText.match(/import\s*\w*\s*from.*\n/)
+      referencesMatches= currentText.match(/\/\/\/\s*<reference\s*path.*\/>\n/g)
+      if importMatches
+        lastImport = importMatches.pop();
+        currentText = currentText.replace(lastImport, lastImport + importStatement);
+      else if referencesMatches
+        lastReference = referencesMatches.pop();
+        currentText = currentText.replace(lastReference, lastReference + '\n' + importStatement);
+      else
+        currentText = importStatement + currentText;
+      editor.setText(currentText);
+      currentPosition.row++;
+      editor.setCursorBufferPosition(currentPosition)
+
   insert: ->
       @bindEvent()
       console.log(@index)
@@ -71,8 +89,8 @@
         fileFolder = path.resolve(filePath + '/..')
         relative = path.relative(fileFolder, symbolLocation).replace(/\.(js|ts)$/, '');
         console.log('filePath, symbolLocation, relative', filePath, symbolLocation);
-        importClause = "\nimport #{selection} from './#{relative}';"
-        editor.setTextInBufferRange([[0,0], [0,0]], importClause + '\n')
+        importClause = "import #{selection} from './#{relative}';\n"
+        @addImportStatement(importClause)
 #        editor.insertText(selection + "\nimport #{selection} from './#{relative}'")
       else
         console.log('No cached data found for symbol', selection)


Let generatePassword works, that was causing infinite loop ∞ <- that one

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -297,7 +297,7 @@
   generatePassword: do ->

     letter = /[a-zA-Z]$/;
-    vowel = /[aeiouAEIOU]$      /;
+    vowel = /[aeiouAEIOU]$/;
     consonant = /[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]$/;

     (length = 10, memorable = yes, pattern = /\w/, prefix = '')->


Terminal: WebtermView: use machineName or machineId instead vmName, remove vmController dependency

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -53,13 +53,16 @@
     window.addEventListener "blur",  => @terminal.setFocused no
     window.addEventListener "focus", => @setFocus @focused

+
     @getElement().addEventListener "mousedown", (event) =>
       @terminal.mousedownHappened = yes
     , yes

     @forwardEvent @terminal, 'command'

-    KD.mixpanel "Open Webterm, click", {vmName}
+    KD.mixpanel "Open Webterm, click", {
+      machineName : @getMachine().getName()
+    }

     @getDelegate().on 'KDTabPaneActive', => @terminal.updateSize yes

@@ -77,90 +80,87 @@
       sizeY       : @terminal.sizeY
       joinUser    : myOptions.joinUser  ? delegateOptions.joinUser
       session     : @sessionId ? myOptions.session ? delegateOptions.session
-      mode        : myOptions.mode      ? 'create'
-
-  getVMName:->
-
-    if vm = @getOption 'vm'
-      { hostnameAlias: vmName } = vm
-
-    vmName = @getDelegate().getOption "vmName"  unless vmName
-
-    return vmName
-
-  getKite: ->
-
-    return (@getOption 'machine').kites.klient
-
-    { kontrol, kiteController, vmController } = KD.singletons
-    vmName = @getVMName()
-    if KD.useNewKites
-      kite = KD.singletons.kontrol.kites.terminal[vmName]
-      return kite  if kite?
-      kontrol.getKite
-        name            : 'terminal'
-        correlationName : vmName
-    else
-      kite = vmController.terminalKites[vmName]
-      return kite  if kite?
-      kiteController.getKite 'terminal', vmName, 'terminal'
+      mode        : myOptions.mode ? 'create'
+
+    return params
+
+
+  getMachine: -> @getOption 'machine'
+  getKite:    -> @getMachine().kites.klient
+

   webtermConnect:(mode = 'create')->

-    return console.info "reconnection is in progress" if @reconnectionInProgress
+    if @reconnectionInProgress
+      return console.info "reconnection is in progress"
+
     @reconnectionInProgress = yes
+
     options = @generateOptions()
     options.mode = mode

-    {vmController, kontrol} = KD.singletons
-
     kite = @getKite()
-
     kite.webtermConnect(options).then (remote) =>
+
       @setOption "session", remote.session
       @terminal.eventHandler = (data)=>
         @emit "WebTermEvent", data
-      @terminal.server       = remote
+
+      @terminal.server = remote
       @sessionId = remote.session

       @emit "WebTermConnected", remote
       @reconnectionInProgress = false

     .catch (err) =>
+
       KD.utils.warnAndLog "terminal: webtermConnect error",
-        {hostnameAlias:@getVMName(), reason:err?.message, options}
+        { hostnameAlias: @getMachine().getName(), reason:err?.message, options }

       if err.code is "ErrInvalidSession"
+
         @reconnectionInProgress = false
         @emit 'TerminalCanceled',
-          vmName: @getVMName()
-          sessionId: @getOptions().session
-          error: err
+          machineId : @getMachine().uid
+          sessionId : @getOptions().session
+          error     : err
+
         return
+
       else
+
         @reconnectionInProgress = false
         throw err

+
   connectToTerminal: ->
+
     @appStorage = KD.getSingleton('appStorageController').storage 'Terminal', '1.0.1'
+
     @appStorage.fetchStorage =>
+
       @appStorage.setValue 'font'      , 'ubuntu-mono' if not @appStorage.getValue('font')?
       @appStorage.setValue 'fontSize'  , 14 if not @appStorage.getValue('fontSize')?
       @appStorage.setValue 'theme'     , 'green-on-black' if not @appStorage.getValue('theme')?
       @appStorage.setValue 'visualBell', false if not @appStorage.getValue('visualBell')?
       @appStorage.setValue 'scrollback', 1000 if not @appStorage.getValue('scrollback')?
       @updateSettings()
+
       {mode} = @getOptions()
       @webtermConnect mode

     KD.getSingleton("kiteController").on "KiteError", (err) =>
-      log "kite error:", err
+
+      warn "kite error:", err
+
       @reconnected = no
       {code, serviceGenericName} = err

-      vmName = (@getOption 'vmName') or @getDelegate().getOption "vmName"
-
-      ErrorLog.create "KiteError", {code, serviceGenericName, vmName, reason: err?.message}
+      machineName = @getMachine().getName()
+
+      ErrorLog.create "KiteError", {
+        code, serviceGenericName, machineName, reason: err?.message
+      }

       if code is 503 and serviceGenericName.indexOf("kite-os") is 0
         @reconnectAttemptFailed serviceGenericName, vmName
@@ -232,21 +232,28 @@

   destroy: ->
     super
+
     KD.utils.killRepeat @checker
+
     unless @status is "fail"
       @emit "TerminalClosed",
-        vmName   : @getVMName()
-        sessionId: @getOptions().session
+        machineId : @getMachine().uid
+        sessionId : @getOptions().session

     @terminal.server?.terminate()

+
   updateSettings: ->
+
     @container.unsetClass font.value for font in __webtermSettings.fonts
     @container.unsetClass theme.value for theme in __webtermSettings.themes
+
     @container.setClass @appStorage.getValue('font')
     @container.setClass @appStorage.getValue('theme')
+
     @container.$().css
       fontSize: @appStorage.getValue('fontSize') + 'px'
+
     @terminal.updateSize true
     @terminal.scrollToBottom(no)
     @terminal.controlCodeReader.visualBell = @appStorage.getValue 'visualBell'


Enable CORS for the node.js server

Enable CORS for the node.js server

--- node.js/server.coffee
+++ node.js/server.coffee
@@ -14,6 +14,9 @@

   req.on('data', console.log)

+  resp.setHeader('Access-Control-Allow-Origin', '*')
+  resp.setHeader('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')
+  resp.setHeader('Access-Control-Allow-Headers', 'accept, cache-control, origin, x-requested-with, x-file-name, content-type')
   resp.writeHead(200)
   resp.end()


some uniquness

some uniquness

--- node/fabricate.coffee
+++ node/fabricate.coffee
@@ -168,7 +168,7 @@
       item_type: 'FeaturedLink'

     when 'user'
-      id: '54276766fd4f50996aeca2b8'
+      id: '54276766fd4f50996aeca2' + _.random(10,99)
       name: 'Craig Spaeth'
       website: null
       email: 'craigspaeth@gmail.com'


Support setting without a selector

Support setting without a selector

--- client/lanes/react/Viewport.coffee
+++ client/lanes/react/Viewport.coffee
@@ -18,6 +18,7 @@
     constructor: ->
         super
         ALL_INSTANCES.push(this)
+        return unless @selector
         @domRoot = document.body.querySelector(@selector)
         _.dom(@domRoot).addClass('lanes-root')
         Lanes.fatal("Root selector #{@selector} not found") unless root


remove whitespace

remove whitespace

--- client/lanes/screens/Router.coffee
+++ client/lanes/screens/Router.coffee
@@ -1,6 +1,4 @@
-
 class Router
     constructor: -> super
-
-
+
 Lanes.Screens.Router = Lanes.lib.MakeBaseClass( Lanes.Vendor.Ampersand.Router, Router )


using right icon

using right icon

--- client/views/login/services.coffee
+++ client/views/login/services.coffee
@@ -15,7 +15,7 @@
 					icon = 'github-circled'
 				when 'gitlab'
 					serviceName = 'Gitlab'
-					icon = 'git-squared'    # need to replace this with proper logo
+					icon = service
 				else
 					serviceName = _.capitalize service
 					icon = service


GetColors: Test that new ports exists

GetColors: Test that new ports exists

--- spec/GetColors.coffee
+++ spec/GetColors.coffee
@@ -55,8 +55,10 @@
     c.outPorts.canvas.attach canvas

   describe 'when instantiated', ->
-    it 'should have an input port', ->
+    it 'should have a input ports', ->
       chai.expect(c.inPorts.canvas).to.be.an 'object'
+      chai.expect(c.inPorts.quality).to.be.an 'object'
+      chai.expect(c.inPorts.colors).to.be.an 'object'
     it 'should have output ports', ->
       chai.expect(c.outPorts.colors).to.be.an 'object'
       chai.expect(c.outPorts.canvas).to.be.an 'object'


Invalid change test on Model for subscriptions added after events occur

--- spec/ModelSpec.coffee
+++ spec/ModelSpec.coffee
@@ -152,8 +152,15 @@
 			runs ->
 				expect(change.callCount).toBe 1

+		it "should not trigger on handlers assigned after the event", ->
+			newChange = sinon.spy()
+			model.set key:"val"
+			model.on "change", newChange
+			waitsFor (->change.called), "Change never called", 100
+			runs ->
+				expect(newChange.callCount).toBe 0
+
 		it "should report the old value"
-		it "should issue change events even if nothing changed"

 	describe "toJSON", ->
 		it "should convert to a json object", ->
@@ -372,7 +379,6 @@
 					data = changeBar.getCall(0).args[0].data
 					expect(data.model).toBe model
 					expect(data.value).toBe relation.get()
-

 			it "should fire when adding to HasMany", ->
 				relation = model.getRelation "bar"


:fire: unnecessary test

:fire: unnecessary test

--- spec/list-spec.coffee
+++ spec/list-spec.coffee
@@ -58,10 +58,6 @@
     fs.makeTreeSync badPackagePath
     fs.writeFileSync path.join(badPackagePath, "file.txt"), "some fake stuff"

-  it 'lists the packages included the _atomPackages section of the package.json', ->
-    listPackages [], ->
-      expect(console.log.argsForCall[1][0]).toContain 'test-module@1.0.0'
-
   it 'lists the installed packages', ->
     listPackages [], ->
       lines = console.log.argsForCall.map((arr) -> arr.join(' '))


KDFramework: list item getItemDataId fixed

--- client/Framework/components/list/KDListItemView.coffee
+++ client/Framework/components/list/KDListItemView.coffee
@@ -45,4 +45,4 @@
     @unsetClass "selected"
     @unsetClass "dimmed"

-  getItemDataId:-> @getData().getId?()+  getItemDataId:-> @getData().getId?() or @getData().id or @getData()._id


StacksCustomViews: fetchRepoFile from remote.api.Github by `repos.getContent`

--- client/admin/lib/views/stacks/stackscustomviews.coffee
+++ client/admin/lib/views/stacks/stackscustomviews.coffee
@@ -144,19 +144,20 @@
       .nodeify callback

-  fetchTemplate = (repo, callback) ->
-
-    {repoData: {full_name}, name, location} = repo
-
-    req_url   = "https://raw.githubusercontent.com/#{full_name}/#{name}#{location}"
-    req       =
-      url     : req_url
-      success : (rest...) -> callback null, rest...
-      error   : (rest...) -> callback rest...
-
-    $.ajax req
-
-    return
+  fetchRepoFile = (options, callback) ->
+
+    { Github } = remote.api
+    { repo, location, ref } = options
+
+    Github.api
+      method  : 'repos.getContent'
+      options :
+        repo  : repo.name
+        user  : repo.owner.login
+        path  : location
+        ref   : ref
+
+    , callback

   updateStackTemplate = (data, callback) ->


Fix isCAseSensitive and isWholeWord

Fix isCAseSensitive and isWholeWord

--- client/ide/views/contentsearch/idecontentsearch.coffee
+++ client/ide/views/contentsearch/idecontentsearch.coffee
@@ -61,8 +61,8 @@
     vmController    = KD.getSingleton 'vmController'
     @searchText     = Encoder.XSSEncode @findInput.getValue()
     @rootPath       = Encoder.XSSEncode @whereInput.getValue()
-    isCaseSensitive = true
-    isWholeWord     = true
+    isCaseSensitive = @caseToggle.getValue()
+    isWholeWord     = @wholeWordToggle.getValue()
     # isRegExp        = @regExpToggle.getValue()


fix vbd for sr thanks to julienf

fix vbd for sr thanks to julienf

--- src/spec.coffee
+++ src/spec.coffee
@@ -337,7 +337,7 @@
           # FIXME: SR.VDIs -> VDI instead of VDI.SR -> SR.
           SR: get('SR')

-          $VBD: get('VBDs')
+          $VBD: (value) -> {VBDs} = value; if VBDs.length is 0 then null else refsToUUIDs[VBDs[0]]

       VBD:


Add apm star --installed

Add apm star --installed

This installs all the packages currently installed to
~/.atom/packages

Closes #130

--- src/star.coffee
+++ src/star.coffee
@@ -1,9 +1,15 @@
+path = require 'path'
+
+_ = require 'underscore-plus'
 async = require 'async'
+CSON = require 'season'
 optimist = require 'optimist'

 config = require './config'
 Command = require './command'
+fs = require './fs'
 Login = require './login'
+Packages = require './packages'
 request = require './request'

 module.exports =
@@ -21,6 +27,7 @@
       Run `apm stars` to see all your starred packages.
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')
+    options.boolean('installed').describe('installed', 'Star all packages in ~/.atom/packages')

   starPackage: (packageName, token, callback) ->
     process.stdout.write '\u2B50  ' if process.platform is 'darwin'
@@ -42,14 +49,34 @@
         @logSuccess()
         callback()

+  getInstalledPackageNames: ->
+    installedPackages = []
+    userPackagesDirectory = path.join(config.getAtomDirectory(), 'packages')
+    for child in fs.list(userPackagesDirectory)
+      continue unless fs.isDirectorySync(path.join(userPackagesDirectory, child))
+
+      if manifestPath = CSON.resolve(path.join(userPackagesDirectory, child, 'package'))
+        try
+          metadata = CSON.readFileSync(manifestPath) ? {}
+          if metadata.name and Packages.getRepository(metadata)
+            installedPackages.push metadata.name
+
+    _.uniq(installedPackages)
+
   run: (options) ->
     {callback} = options
     options = @parseOptions(options.commandArgs)
-    packageNames = @packageNamesFromArgv(options.argv)

-    if packageNames.length is 0
-      callback("Must specify a package name to star")
-      return
+    if options.argv.installed
+      packageNames = @getInstalledPackageNames()
+      if packageNames.length is 0
+        callback()
+        return
+    else
+      packageNames = @packageNamesFromArgv(options.argv)
+      if packageNames.length is 0
+        callback("Must specify a package name to star")
+        return

     Login.getTokenOrLogin (error, token) =>
       return callback(error) if error?


Add mock class attributes @signatures and @state to instances lazily.

Was being done in the constructor but now being done by the various
mock methods.  This allows Mock to be more easily used as a mixin
when we want to mock an existing class (we don't have to worry
about monkeying with the existing class' constructor to add
@signatures and @state).

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -96,11 +96,12 @@
 # @signatures is just an array, not a hash.  It will not grow very large
 # so a linear search for a particular signature is fine.
 #
+# @signatures and @state are added to the mock object by the various mock
+# methods when needed (i.e. lazily); if we mock an existing class then we
+# only need to worry about adding the mock methods to that class, not
+# monkeying with its contructor to also add @signatures and @state.
+#
 class Mock
-
-  constructor: ->
-    @signatures = []
-    @state = undefined

   #
   # my_mock = (new Mock).expects("my_method")
@@ -152,6 +153,9 @@
 # Private Mock 'methods'; making them functions keeps the Mock object uncluttered.
 #

+_signatures = (mock) ->
+  mock.signatures ?= []
+
 _set_state = (mock, state) ->
   mock.state = state

@@ -179,20 +183,20 @@

 _check_for_duplicate_signatures = (mock) ->
   # TODO: use each with index and slice to avoid last element
-  signatures = mock.signatures
+  signatures = _signatures(mock)
   return if signatures.length < 2
   for outer in [0..signatures.length-2]
     for inner in [outer+1..signatures.length-1]
       _throw_duplicate_expectation("#{signatures[outer].method_name}(#{signatures[outer].args})") if signatures[outer].equals( signatures[inner] )

 _current_signature = (mock) ->
-  mock.signatures[0]
+  _signatures(mock)[0]

 _current_method_name = (mock) ->
   _current_signature(mock)?.method_name

 _find_signature = (mock, method_name, args...) ->
-  for signature in mock.signatures when signature.matches(method_name, args...)
+  for signature in _signatures(mock) when signature.matches(method_name, args...)
     return signature
   undefined

@@ -207,12 +211,12 @@

 _build_errors = (mock) ->
   errors = ""
-  for signature in mock.signatures when signature.called == false
+  for signature in _signatures(mock) when signature.called == false
     errors += "'#{signature.method_name}(#{signature.args})' was never called\n"
   errors

 _start_new_signature = (mock, method_name) ->
-  mock.signatures.unshift( new MethodSignature(method_name) )     # .unshift pushes to front of array
+  _signatures(mock).unshift( new MethodSignature(method_name) )     # .unshift pushes to front of array

 _add_method_to_mock = (mock, method_name) ->
   mock[ method_name ] ?= _build_mocked_method(mock, method_name)


Fix displaying following status on ProfileView

--- client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
+++ client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
@@ -54,6 +54,16 @@
       ]
     , memberData

+    unless memberData.followee
+      KD.whoami().isFollowing? memberData.getId(), "JAccount", (following) =>
+        memberData.followee = following
+        if memberData.followee
+          #@followButton.setClass 'following-btn following-topic'
+          @followButton.setState "Unfollow"
+        else
+          @followButton.setState "Follow"
+          #@followButton.unsetClass 'following-btn following-topic'
+
     @skillTags = @putSkillTags()

     @followers = new KDView


Fallback to default app icon

Fallback to default app icon

--- client/landing/app/Applications/Apps.kdapplication/Views/AppsListItemView.coffee
+++ client/landing/app/Applications/Apps.kdapplication/Views/AppsListItemView.coffee
@@ -12,12 +12,13 @@
     else
       thumb = "#{KD.apiUri + '/images/default.app.listthumb.png'}"

-    thumbOptions =
-      tagName     : 'img'
+    @thumbnail = new KDCustomHTMLView
+      tagName     : "img"
+      bind        : "error"
+      error       : =>
+        @thumbnail.$().attr "src", "/images/default.app.listthumb.png"
       attributes  :
         src       : thumb
-
-    @thumbnail = new KDCustomHTMLView thumbOptions

   click:(event)->
     if $(event.target).is ".appdetails h3 a span"


Remove unused param

Remove unused param

--- app/assets/javascripts/commits.js.coffee
+++ app/assets/javascripts/commits.js.coffee
@@ -1,7 +1,7 @@
 class @CommitsList
   @timer = null

-  @init: (ref, limit) ->
+  @init: (limit) ->
     $("body").on "click", ".day-commits-table li.commit", (event) ->
       if event.target.nodeName != "A"
         location.href = $(this).attr("url")


Logged Out/Logged In redirects

Logged Out/Logged In redirects

--- app/assets/javascripts/controller.coffee
+++ app/assets/javascripts/controller.coffee
@@ -1,18 +1,32 @@
 do (App = @Lunchiatto) ->
+  forLoggedInOnly = ->
+    return false if App.currentUser.loggedIn()
+    App.router.navigate("/", {trigger: true})
+    true
+
+  maybeRedirectToToday = ->
+    return false unless App.currentUser.loggedIn()
+    App.router.navigate("/orders/today", {trigger: true})
+    true
+
   App.Controller =
     pageIndex: ->
+      return if maybeRedirectToToday()
       App.Page.Controller.index()

     ordersToday: (orderId) ->
+      return if forLoggedInOnly()
       App.Today.Controller.today(orderId)

     editDish: (orderId, dishId) ->
+      return if forLoggedInOnly()
       dish = new App.Entities.Dish(order_id: orderId, id: dishId)
       dish.fetch
         success: (dish) ->
           App.Dish.Controller.form(dish)

     newDish: (orderId) ->
+      return if forLoggedInOnly()
       dish = new App.Entities.Dish
         order_id: orderId,
         user_id: App.currentUser.id,
@@ -20,46 +34,57 @@
       App.Dish.Controller.form(dish)

     newOrder: ->
+      return if forLoggedInOnly()
       order = new App.Entities.Order(shipping: '0.00')
       App.Order.Controller.form(order)

     showOrder: (orderId) ->
+      return if forLoggedInOnly()
       order = new App.Entities.Order(id: orderId)
       order.fetch
         success: (order) ->
           App.Order.Controller.show(order)

     editOrder: (orderId) ->
+      return if forLoggedInOnly()
       order = new App.Entities.Order(id: orderId)
       order.fetch
         success: (order) ->
           App.Order.Controller.form(order)

     ordersIndex: ->
+      return if forLoggedInOnly()
       orders = new App.Entities.Orders
       orders.fetch
         success: (orders) ->
           App.Order.Controller.list(orders)

     yourBalances: ->
+      return if forLoggedInOnly()
       App.Balance.Controller.you()

     othersBalances: ->
+      return if forLoggedInOnly()
       App.Balance.Controller.others()

     accountNumbers: ->
+      return if forLoggedInOnly()
       App.Dashboard.Controller.accounts()

     settings: ->
+      return if forLoggedInOnly()
       App.currentUser.fetch
         success: (user) ->
           App.Dashboard.Controller.settings(user)

     transfersIndex: ->
+      return if forLoggedInOnly()
       App.Transfer.Controller.index()

     newTransfer: ->
+      return if forLoggedInOnly()
       App.Transfer.Controller.form()

     members: ->
+      return if forLoggedInOnly()
       App.User.Controller.members()


break responses

break responses

--- app/assets/javascripts/directives.coffee
+++ app/assets/javascripts/directives.coffee
@@ -152,7 +152,7 @@
         if !!node.responses
           text = text + "Responses:\n\n"
           _.each node.responses, (r) ->
-            text = text + r + "\n\n"
+            text = text + break_lines(r[0]) + ", " + r[1] + "\n\n"

         maxLineLength = _.max(text.split('\n'), (l) ->
           l.length
@@ -248,7 +248,7 @@
         root = get_root(source_h, target_h)

         flare = {name: root, children: [_.uniq source_h[root]]}
-        console.log flare
+        # console.log flare
         # _.each _.keys(source_h), (k) ->
         #   _.each _.keys(source_h), (k2) ->
         #     #console.log $.inArray(k, source_h[k2])        console.log root


Clear original input after crop

Clear original input after crop

This fixes the case when the user selects the same image consecutively. It failed because no 'change' event was fired.

--- app/assets/javascripts/gl_crop.js.coffee
+++ app/assets/javascripts/gl_crop.js.coffee
@@ -105,6 +105,7 @@
     @setBlob()
     @setPreview()
     @modalCrop.modal('hide')
+    @fileInput.val('')

   onActionBtnClick: (btn) ->
     data = $(btn).data()


tidy up motion.js

tidy up motion.js

--- app/assets/javascripts/motions.js.coffee
+++ app/assets/javascripts/motions.js.coffee
@@ -65,32 +65,6 @@
   #** prevent expansion of motion **
   $(".no-toggle").click((event) ->
     event.stopPropagation()
-  )
-
-  #** limit character count for statement**
-  pluralize_characters = (num) ->
-    if(num == 1)
-      return num + " character"
-    else
-      return num + " characters"
-
-  $("#limited").keyup(() ->
-    chars = $("#limited").val().length
-    left = 249 - chars
-    if(left >= 0)
-      $(".char_count").text(pluralize_characters(left) + " left")
-      $(".clearfix").removeClass("error")
-    else
-      left = left * (-1)
-      $(".char_count").text(pluralize_characters(left) + " too long")
-      $(".clearfix").addClass("error")
-  )
-
-  $(".vote").click((event) ->
-    if $(".clearfix").hasClass("error")
-      $('#new_vote').preventDefault()
-    else
-      $('#new_vote').submit()
   )

   #** limit character count for statement**


Bugfix: proposal close date validation.

Bugfix: proposal close date validation.

--- app/assets/javascripts/motions.js.coffee
+++ app/assets/javascripts/motions.js.coffee
@@ -34,34 +34,38 @@
       $("#input_date").datepicker("setDate", date_string)
       $("#date_hour").val(hour)

+
 # Reload hidden close_date field
 $ ->
   $("#input_date").change((e) ->
-    remove_date_error()
-    date = $(this).val()
-    local_datetime = new Date()
-    local_datetime.setYear(parseInt(date.substring(6,10)))
-    local_datetime.setMonth((parseInt(date.substring(3,5)) - 1), parseInt(date.substring(0,2)))
-    local_datetime.setHours(parseInt($("#date_hour").val()))
-    $("#motion_close_date").val(local_datetime)
+    set_close_date()
   )

 $ ->
   $("#date_hour").change((e) ->
-    remove_date_error()
-    date = $("#input_date").val()
-    local_datetime = new Date()
-    local_datetime.setYear(parseInt(date.substring(6,10)))
-    local_datetime.setMonth((parseInt(date.substring(3,5)) - 1), parseInt(date.substring(0,2)))
-    local_datetime.setHours(parseInt($(this).val()))
-    $("#motion_close_date").val(local_datetime)
+    set_close_date()
   )
+
+set_close_date = ->
+  remove_date_error()
+  date = $("#input_date").val()
+  local_datetime = new Date()
+  local_datetime.setYear(parseInt(date.substring(6,10)))
+  month = date.substring(3,5)
+  month = month[1] if month[0] == "0"
+  day = date.substring(0,2)
+  day = day[1] if day[0] == "0"
+  local_datetime.setMonth(parseInt(month) - 1, parseInt(day))
+  local_datetime.setHours(parseInt($("#date_hour").val()))
+  $("#motion_close_date").val(local_datetime)
+

 # The following methods are used to provide client side validation for
 # - character count
 # - presence required
 # - date validation specific for motion-form
-remove_date_error = () ->
+
+remove_date_error = ->
   $(".validate-motion-close-date").parent().removeClass("error")
   $(".date-error-message").hide()


Small fix: typos

Small fix: typos

--- src/trie.coffee
+++ src/trie.coffee
@@ -6,9 +6,9 @@
   result = []
   counter = 0
   length = str.length
-  if str.codepointat?
+  if str.codePointAt?
     while counter < length
-      codepoint = str.codepointat counter
+      codepoint = str.codePointAt counter
       if codepoint?
         result.push codepoint
         counter += if codepoint > 0xffff then 2 else 1


Fix misc tests

Fix misc tests

--- test/misc.coffee
+++ test/misc.coffee
@@ -126,8 +126,9 @@
         mecano.mkdir
           destination: "#{scratch}/remove_dir"
         , (err, created) ->
-          next err if err
+          return next err if err
           misc.file.remove null, "#{scratch}/remove_dir", (err) ->
+            return next err if err
             misc.file.exists null, "#{scratch}/remove_dir", (err, exists) ->
               return next err if err
               exists.should.not.be.ok
@@ -139,8 +140,9 @@
             ssh: ssh
             destination: "#{scratch}/remove_dir"
           , (err, created) ->
-            next err if err
+            return next err if err
             misc.file.remove ssh, "#{scratch}/remove_dir", (err) ->
+              return next err if err
               misc.file.exists ssh, "#{scratch}/remove_dir", (err, exists) ->
                 return next err if err
                 exists.should.not.be.ok


Remove unused `lookup_handler`.

Remove unused `lookup_handler`.

--- src/util.coffee
+++ src/util.coffee
@@ -216,17 +216,6 @@
   else 0

 root.is_string = (obj) -> typeof obj == 'string' or obj instanceof String
-
-# Walks up the prototype chain of :object looking for an entry in the :handlers
-# dict that match its constructor's name.
-root.lookup_handler = (handlers, object) ->
-  obj = object
-  while obj?
-    # XXX: this will break on IE (due to constructor.name being undefined)
-    handler = handlers[obj.constructor.name]
-    return handler if handler
-    obj = Object.getPrototypeOf obj
-  return null

 # Java classes are represented internally using slashes as delimiters.
 # These helper functions convert between the two representations.


read mode properly from stats

read mode properly from stats

--- src/wrap.coffee
+++ src/wrap.coffee
@@ -160,7 +160,7 @@
             if path.existsSync(file)
                 stats = fs.statSync(file)
                 opts.size  = stats.size
-                opts.mode ?= parseInt(stats.mode.toString(8)[2 ..])
+                opts.mode ?= stats.mode & 0o0777
             else if typeof content is 'string'
                 opts.size = new Buffer(content, opts.encoding ? 'utf-8').length
             @emit('error', "no size for #{file}") unless opts.size?


KDSplitView: Added merge method.

KDSplitView: Added merge method.

--- src/components/split/splitview.coffee
+++ src/components/split/splitview.coffee
@@ -304,6 +304,18 @@
     @_resizeDidStop()
     @beingResized = no

+  merge: ->
+    {views} = @getOptions()
+    @getOptions().views = []
+
+    views.forEach (view, i) =>
+      return  unless view
+      view.detach()
+      view.unsetParent()
+      @panels[i].subViews = []
+
+    @emit "SplitIsBeingMerged", views
+    @destroy()

   removePanel:(index)->