Literate documentation of branch page files.

--- src/lib/strata_.coffee
+++ src/lib/strata_.coffee
@@ -15,7 +15,7 @@
 #
 # But this isn't important at the moment.
 #
-# ### Terminology
+# ## Terminology
 #
 # We refer to the nodes in our b-tree as *pages*. The term node conjures an
 # image of a discrete component in a linked data structure that contains one,
@@ -37,7 +37,7 @@
 # Terms specific to our implementation will be introduced as they are
 # encountered in the document.
 #
-# ### What is a b-tree?
+# ## What is a b-tree?
 #
 # This documentation assumes that you understand the theory behind the b-tree,
 # and know the variations of implementation. If you are interested in learning
@@ -46,7 +46,7 @@
 # [B-trees](http://en.wikipedia.org/wiki/B-tree) and
 # [B+trees](http://en.wikipedia.org/wiki/B%2B_tree).
 #
-# ### What flavor of b-tree is this?
+# ## What flavor of b-tree is this?
 #
 # Strata is a b-tree with leaf pages that contain records ordered by the
 # collation order of the tree, indexed for retrieval in constant time, so that
@@ -65,11 +65,11 @@
 # split of the root branch is a different operation from the split of a non-root
 # branch, because the root branch does not have siblings.
 #
-# ### Page Structure
+# ## Implementation
 #
 # The b-tree has two types of pages. Leaf pages and branch pages.
 #
-# #### Leaf Pages
+# ### Leaf Pages
 #
 # Leaf pages contain records.
 #
@@ -93,7 +93,7 @@
 # since no suitable partition exists. The page will be allowed to grow beyond
 # the maximum page size.
 #
-# #### Branch Pages
+# ### Branch Pages
 #
 # Branch pages contain links to other pages. To find the a record in the b-tree,
 # we first use branch pages to find the leaf page that contains our record.
@@ -111,7 +111,7 @@
 # There are two special types of branch pages, the root page, and penultimate
 # pages.
 #
-# ##### Root Page
+# #### Root Branch Page
 #
 # The root page is the first page we consult to find the desired leaf page. Our
 # b-tree always contains a root page. The b-tree is never so empty that the root
@@ -120,7 +120,7 @@
 # TK move. Until the root branch page is split, it is both the root branch page
 # and a penultimate branch page.
 #
-# ##### Penultimate Pages
+# #### Penultimate Branch Pages
 #
 # A penultimate branch page is a branch page whose children are leaf pages. If a
 # branch page is not penultimate, then its children are branch pages.
@@ -153,7 +153,7 @@
 # An insertion can only insert a into the left most leaf page of a penumltimate
 # branch page a record less than the least record of the leaf page.
 #
-# ##### Interior Branch Pages
+# #### Interior Branch Pages
 #
 # A branch page whose children are other branch pages is called an interior
 # branch page.
@@ -197,7 +197,7 @@
 # right penultimate page, we visit the right penultimate page, then we visit its
 # left child, the leaf page whose first record is the key.
 #
-# #### Keys and First Records
+# ### Keys and First Records
 #
 # We said that it is only possible for an insertion to insert a into the left
 # most child leaf page of a penumltimate branch page a record less than the least
@@ -277,486 +277,30 @@
 # Default extractor returns the value as hole, i.e. tree of integers.
 extractor = (a) -> a

-# Glossary:
-#
-#  * Descent - What we call an attempt to move from a parent node to a child
-#              node in  the tree, which may be paused because it encounters a
-#              lock on a tier.
-#  * Record - A JSON object stored in the b-tree.
-#  * Object - JavaScript objects are referred to as objects in this
-#             documentation and are not to be confused with actual records.
-
-#
-class Mutation
-  # The constructor always felt like a dangerous place to be doing anything
-  # meaningful in C++ or Java.
-  constructor: (@strata, @object, @key, @operation) ->
-    @io         = @strata._io
-    @options    = @strata._options
-
-    @parent     = { operations: [], locks: [] }
-    @child      = { operations: [], locks: [] }
-    @exclusive  = []
-    @shared     = []
-    @pivots     = []
-
-    { @extractor, @comparator } = @options
-
-    @key        = @extractor @object if @object? and not key?
-
-  # Descend the tree, optionally starting an exclusive descent. Once the descent
-  # is exclusive, all subsequent descents are exclusive.
-
-  # `mutation.descend(exclusive)`
-  descend: (exclusive) ->
-    # Add a new level to the plan.
-    @plan.unshift { operations: [], addresses: [] }
-
-    # Two or more exclusive levels mean that the parent is exclusive.
-    if @exclusive.length < 1
-      @strata._release @parent
-
-    # Any items in the exclusive level array means we are now locking exclusive.
-    exclusive or= @exclusive.length > 0
-
-    # Make child a parent.
-    @parent = @child
-    @child = { operations: [], locks: [], exclusive }
-
-    # Add the new child to the list of exclusive levels.
-    if exclusive
-      @exclusive.push(@child)
-
-  # Search for a value in a tier, returning the  index of the value or else
-  # where it should be inserted.
-  #
-  # There is some magic here, long forgotten at the time of documentation. The
-  # tree beneath each branch in an inner tier contains records whose value is
-  # equal to or greater than the pivot. Thus, the pivot of the first record on
-  # an inner tier is null, indicating that that is the branch for all values
-  # less than the value of the first branch with a real pivot.
-  #
-  # TODO: Reading through the code, this does not take this into account, and
-  # will problably send items that belong in the least value tree into the three
-  # that follows it. I'll clean this up with unit testing.
-
-  # `mutation.find(tier, key, callback)`
-  find: (tier, key, _) ->
-    size = tier.addresses.length
-    if tier.leaf
-      [ low, high, leaf ] = [ 0, size - 1, true ]
-    else if size is 1
-      return 0
-    else
-      [ low, high ] = [ 1, size - 1 ]
-    { comparator, io } = @
-    loop
-      mid = (low + high) >>> 1
-      compare = comparator key, io.key(tier, mid, _)
-      if compare is 0
-        break
-      if compare > 0
-        low = mid + 1
-      else
-        high = mid - 1
-    if compare is 0
-      if leaf
-        while mid != 0 && comparator(key, io.key(tier, mid - 1, _)) == 0
-          mid--
-      mid
-    else if branches
-      mid - 1
-    else
-      mid
-
-  hasKey: (tier, _) ->
-    for key in @operation.keys or []
-      die { key }
-      branch = @find(tier, key, _)
-      if tier.cache[branch] is key
-        return true
-    false
-
-  # TODO: We are always allowed to get a shared lock on any leaf page and read a
-  # value, as long as we let go of it immediately. This allows us to read from
-  # leaf pages to get branch values.
-  # TODO: Rename this descend.
-
-  # `mutation.mutate(callback)`
-  mutate: (_) ->
-    parent = null
-
-    @shared.push child = @io.lock false, 0, false, _
-    if @operation.keys and (child.addresses.length is 1 or @hasKey(child, _))
-      @exclusive.push @io.upgrade @shared.pop(), _
-
-    # TODO: Flag for go left.
-    while not child.penultimate or child.address is @soughtAddress
-      process.exit 1
-
-    # TODO: Flag for shared or exclusive. If exclusive, leave parent locked, if
-    # shared, then unlock parent.
-
-    mutation = @[@operation.method].call this, parent, child, _
-
-    @io.release tier for tier in @shared
-    @io.release tier for tier in @exclusive
-
-    mutation.mutate _ if mutation
-
-  insertSorted: (parent, child, _) ->
-    branch = @find child, @key, _
-    @exclusive.push leaves = @io.lock true, child.addresses[branch], true, _
-
-    addresses = leaves.addresses
-
-    # Insert the object value sorted.
-    for i in [0...addresses.length]
-      key = @io.key(leaves, i, _)
-      if @comparator(@key, key) <= 0
-        break
-
-    address = leaves.io.writeInsert @object, i, _
-    addresses.splice i, 0, address
-
-    if addresses.length > @options.leafSize and not @homogenous(leaves, _)
-      keys = []
-      process.nextTick _
-      # Opportunity to deadlock.
-      keys.push key = @io.key leaves, 0, _
-      if leaves.right
-        keys.push @io.key leaves.right, 0, _
-      operation =
-        method: "splitLeaf"
-        keys: keys
-      new Mutation(@strata, null, key, operation)
-
-  # If the leaf tier is now at the maximum size, we test to see if the leaf tier
-  # is filled with an identical key value, and if not, we split the leaf tier.
-  homogenous: (tier, _) ->
-    { io, comparator } = @
-    first = io.key(tier, 0, _)
-    last = io.key(tier, tier.addresses.length - 1, _)
-    comparator(first, last) is 0
-
-  get: (parent, child, _) ->
-    branch = @find child, @key, _
-    @shared.push leaves = @io.lock false, child.addresses[branch], true, _
-    address = @find leaves, @key, _
-    leaves.cache[leaves.addresses[address]]
-
-  splitLeaf: (parent, child, _) ->
-    branch = @find child, @key, _
-    @exclusive.push leaves = @io.lock true, child.addresses[branch], true, _
-
-    # See that nothing has changed since we last descended.
-    { comparator: c, io, options: { leafSize: length } } = @
-    return if c(io.key(leaves, 0, _), @key) isnt 0
-    return if @homogenous(leaves, _)
-    return if leaves.addresses.length < @options.leafSize
-
-    # We might have let things go for so long, that we're going to have to split
-    # the page into more than two pages.
-    partitions = Math.floor leaves.addresses.length / @options.leafSize
-    while (partitions * @options.leafSize) <= leaves.addresses.length
-      # Find a partition.
-      mid = Math.floor leaves.addresses.length / (partitions + 1)
-      key = io.key leaves, mid, _
-      [ before, after ] = [ mid - 1, mid + 1 ]
-      while not partition
-        if before > 0 and c(io.key(leaves, before, _), key) isnt 0
-          partition = before + 1
-        else if after <= length and c(io.key(leaves, after, _)) isnt 0
-          partition = after
-        else
-          --before
-          ++after
-
-      key = io.key leaves, partition, _
-      pivot = @find(child, key, _) + 1
-
-      addresses = leaves.addresses.splice(partition)
-      right = io.allocateLeaves(addresses, leaves.right)
-
-      leaves.right = right.address
-
-      child.addresses.splice(pivot, 0, right.address)
-
-      # Append an operation indciator. This would be a record that describes all
-      # the participants in the rewrite, in the order in which they are supposed
-      # to be rewritten. When found, we look at the last item in the list, then
-      # load that, and ensure that the last record is a complete operation
-      # record. If it is, we know that all of the temporary pages were written.
-      @io.rewriteLeaves(leaves, _)
-      @io.rewriteLeaves(right, _)
-
-      @io.rewriteBranches(child, _)
-
-      @io.relink(right, _)
-      @io.relink(leaves, _)
-      @io.relink(child, _)
-
-      --paritions
-
-  mergeLeaf: (parent, child, _) ->
-    [ key, rightKey ] = @operation.keys
-    compare = @comparator(full, leftKey = @io.key(child, 0, _))
-    if compare is 0
-      return if leftKey isnt key
-
-class Level
-  # Construct a new level. The `exclusive` pararameter determines how this level
-  # locks tiers when asked to lock tiers.
-  constructor: (@exclusive) ->
-    @operations = []
-    @locks = []
-
-  # Convert lock to read lock and tell anyone waiting that they can go ahead.
-  #
-  # We give the next tick a copy of just the continuations to fire, excluding
-  # our own continuation which we're about to add. (TODO rename continuations?)
-  #
-  # If you're wondering, no, you're not going to fire the continuations twice.
-  # They are only ever fired by the decent that holds the exclusive lock. This
-  # level will be removed from the level list, so we won't grab at it when the
-  # operations are over.
-  downgrade: (strata) ->
-    if @exclusive
-      while @locks.length
-        lock = @locks.shift()
-        locks = strata.locks[lock.address]
-        throw "not locked by me" if locks[0][0] isnt lock
-        locks.shift()
-        @_resume strata, locks[0].slice(0)
-        locks[0].push lock
-      @exclusive = false
-
-  # Unused.
-  advance: (strata) ->
-    locks = strata.locks[@tier.id]
-    if locks[0].length is 0 and locks.length isnt 1
-      locks.shift()
-      true
-    false
-
-# ### B-Tree Structure
-#
-# We call page of the b-tree, ah, maybe we stop using the word tier, start using
-# the word page, because we're not going to have to reduce the ambiguity with
-# some sort of subdivided binary file structure.
-#
-# NOTE Come back and look at this. It is succinct.
-#
-# There are two types of pages. Branch pages and leaf pages.
-#
-# Leaf pages store the records. They maintain the collation
-#
-# Leaf pages are all uniformly the same.
-#
-# ### Leaf Tier Files
-#
-# A leaf tier maintains an array of file positions called an address array. A
-# file position in the address array references a record in the leaf tier file
-# by its file position. The addresses in the address array are sorted by the
-# the sort order of the referenced records according to the sort order of the
-# b-tree.
-#
-# A leaf tier file contains JSON objects, one object on each line.
-#
-# There are three types of objects in a leaf tier file, insert objects, delete
-# objects, and address array objects.
-#
-# An insert object contains a *record* and the index in the address array where
-# the record's address would be inserted to preserve the sort order of the
-# address array.
-#
-# Beginning with an empty array and reading from the start of the file, the leaf
-# tier address array is reconstituted by reading the object at the current
-# position in the file, then inserting the position of the current object into
-# the address array at the index in the insert object.
-#
-# We record a deletion by appending a delete object. A delete object contains
-# an index in the address array that should be deleted. When reading the leaf
-# tier from the start of the file, we insert an address into the address array
-# when we encounter an insert object, and we delete an address when we encounter
-# a delete object.
-#
-# On occasion, we can store an address array object, An address array object
-# contains the address array itself.  We store a copy of a constructed address
-# array object in the leaf tier file so that we can read a large leaf tier file
-# quickly.
-#
-# When we read a leaf tier file, if we read from the back of the file toward the
-# front, we can read backward until we find an address array object. Then we can
-# read forward to the end of the file, applying the inserts and deletes that
-# occured after we wrote the address array object.
-#
-# When a leaf tier file is large, stashing the constructed address array at the
-# end means that the leaf tier can be loaded quickly, because we will only have
-# to read backwards a few entries to find a mostly completed address array. We
-# can then read forward from the array to amend the address array with the
-# inserts and deletes that occured after it was written.
-#
-# Not all of the records will be loaded when we go backwards, but we have their
-# file position from the address array, so we can jump to them and load them as
-# we need them. That is, if we need them, because owing to binary search, we
-# might only need a few records out of a great many records to find the record
-# we're looking for.
-#
-# Over time, a leaf tier file can grow fat with deleted records — an
-# insert object and a subsequent delete object. We vacuum a leaf tier file by
-# writing it to a new leaf tier file.
-
-# `LeafIO` — Keeps track of a file descriptor opened for append, and the
-# append file position. The `LeafIO` class does *not* contain all methods
-# relating to leaf tier file I/O. You can find more leaf tier file methods in
-# the `IO` class below.
-#
-# TODO Rename `RecordIO`.
-class LeafIO
-  # Construct a leaf file tier writer for the given file name.
-  constructor: (@filename) ->
-    @length = 1024
-
-  # #### Leaf Tier File Object Format
-  #
-  # Each leaf tier object is a JSON array. The first element of the array is an
-  # integer.
-  #
-  # If the integer is greater than zero, it indicates an insert object.  The
-  # integer is the one based index into the zero based address array, indicating
-  # the index where the position of the current insert object should be
-  # inserted. The second element of the leaf tier object array is the record
-  # object.
-
-  # Write an insert object.
-  writeInsert: (object, index, _) ->
-    @_writeJSON [ index + 1, object ], _
-
-  # If the integer is less than zero, it indicates a delete object. The absolute
-  # value of the integer is the one based index into the zero based addrss
-  # array, indicating the index of address array element that should be deleted.
-  # There are no other elements in the leaf tier object array.
-
-  # Write a delete object.
-  writeDelete: (index, _) ->
-    @_writeJSON [ -(index + 1) ], _
-
-  # If the integer is zero, it indicates an address array object. The integer
-  # value of zero is used only as an indicator. The second element is the
-  # address of the leaf tier to the right of current leaf tier. The third
-  # element of the leaf tier object array is the address array.
-
-  # Write an address array object.
-  writeAddresses: (right, addresses, _) ->
-    @_writeJSON [ 0, right, addresses ], _
-
-  # Append an object to the leaf tier file as a single line of JSON.
-  _writeJSON: (object, _) ->
-    @fd           or= fs.open @filename, "a+", 0644, _
-    @position     or= fs.stat(@filename, _).size
-
-    json            = JSON.stringify object
-    position        = @position
-    length          = Buffer.byteLength(json, "utf8")
-    buffer          = new Buffer(length + 1)
-    offset          = 0
-
-    # Write JSON and newline.
-    buffer.write json
-    buffer[length] = 0x0A
-
-    # Write may be interrupted by a signal, so we keep track of how many bytes
-    # are actually written and write the difference if we come up short.
-    while offset != buffer.length
-      count = buffer.length - offset
-      written = fs.write @fd, buffer, offset, count, @position, _
-      @position += written
-      offset += written
-
-    # What's the point if it doesn't make it to disk?
-    fs.fsync @fd, _
-
-    # Return the file position of the inserted record.
-    position
-
-  # Each line is terminated by a newline. In case your concerned that this
-  # simple search will mistake a byte inside a multi-byte character for a
-  # newline, have a look at
-  # [UTF-8](http://en.wikipedia.org/wiki/UTF-8#Description). All bytes
-  # participating in a multi-byte character have their leading bit set, all
-  # single-byte characters have their leading bit unset.
-
-  # Search for the newline that separates JSON records.
-  _readJSON: (buffer, read) ->
-    for offset in [0...read]
-      if buffer[offset] is 0x0A
-        return JSON.parse buffer.toString("utf8", 0, offset + 1)
-
-  # Jump to a position in the file and read a specific object. Because objects
-  # are cached in memory by a leaf tier, we're not going to get a request to
-  # read an object that has been written to the current write stream. When we
-  # read in a leaf tier, we'll cache any objects we encounter prior to read the
-  # ordering array.
-  #
-  # Note how we allow we keep track of the minimum buffer size that will
-  # accommodate the largest possible buffer.
-  readObject: (address, _) ->
-    @fd or= fs.open @filename, "a+", _
-    @position or= fs.stat(@filename, _).size
-    loop
-      buffer = new Buffer(@length)
-      read = fs.read @in, buffer, 0, buffer.length, address, _
-      if json = @_readJSON(buffer, read)
-        break
-      if @length > @position - address
-        throw new Error "cannot find end of record."
-      @length += @length >>> 1
-    json.pop()
-
-  # Close the file descriptor if it is open and reset the file descriptor and
-  # append position.
-  close: (_) ->
-    fs.close(@fd, _) if @fd
-    @position = @fd = null
-
-# ### Tier File Storage
-#
-# Tiers are identified by a number tier address. The tier address is a number
-# that is incremented as new tier are created. A branch tier file has a file
-# name that includes the tier address. The file name is derived from the tier
-# address. When we load a tier, we first derive the file name from the tier
-# address, then we load the file.
-#
-# The leaf tier file contains insert objects, delete objects and address array
-# objects, stored as JSON, one object per line, as described above.
-#
-# The branch tier file contains a single object that includes the array of
-# tier addresses. The tier addresses reference leaf tier files. The tier
-# addresses are sorted according to the value of the objects in leaf tiers they
-# reference. The addresses are sorted by the tree collation using the record
-# from the referenced leaf tier that has the least value according to the tree
-# collation.
-#
-# Put another way, each branch element in a branch tier represents a leaf tier.
-# The leaf tiers are sorted by the tree collation. The object value used to sort
-# the branch tier is first element in the sorted leaf tier.
-#
-# TODO Okay, what? I'm forgetting that I need the addresses to inner tiers as
-# well as to leaf tiers, both, when the tier is not penultimate. Descending is
-# not going to work, that is going to load the entire tree into memory quickly.
-#
-# Hmm... Maybe not. Tree four deep means loading only a particular path. It
-# ensures that we read lock our way down the tree to get our record. Although, I
-# believe I thought long and hard about this, and jumping down to the leaves
-# wasn't a problem.
-#
-# We only need to store the sorted array of the leaf tier addresses in our
-# branch tier, since we can obtain the object used to collate the address
+# ## Page Storage
+#
+# A *leaf page file* contains insert objects, delete objects and address array
+# objects, stored as JSON, one object per line, as described above. The JSON
+# objects stored on behalf of the client are called *records* and they are
+# contained within the insert objects.
+#
+# A *branch page file* contains a single JSON object stored on a single line
+# that contains the array of child page addresses.
+#
+# The `IO` class manages the wholesale reading and writing of page files. The
+# `RecordIO` class manages the insertion and deletion of individual records in a
+# leaf page file.

 #
 class IO
-  # Set directory and extractor. Initialze cache and MRU list.
+  # Each page is stored in its own file. The files are all kept in a single
+  # directory. The directory is specified by the client when the database object
+  # is constructed.
+  #
+  # The `IO` class needs the `extractor` to extract keys from records. It does
+  # not store the `extractor`, of course.
+
+  # Set directory and extractor. Initialze the page cache and MRU list.
   constructor: (@directory, @extractor) ->
     @cache          = {}
     @head           = { address: -1 }
@@ -764,6 +308,92 @@
     @head.previous  = @head
     @nextAddress    = 0

+  # Pages are identified by an integer page address. The page address is a number
+  # that is incremented as new pages are created. A page file has a file name that
+  # includes the page address.  When we load a page, we first derive the file name
+  # from the page address, then we load the file.
+  #
+  # The `filename` method accepts a suffix, so that we can create replacement
+  # files. Instead of overwriting an existing page file, we create a replacement
+  # with the suffix `.new`. We then delete the existing file and rename the
+  # replacement file using the `relink` method. This two step write is part of
+  # our crash recovery strategy.
+  #
+  # We always write out entire branch page files. Leaf pages files are updated
+  # by appending, but on occasion we rewrite them to vaccum deleted records.
+
+  # Create a file name for a given address with an optional suffix.
+  filename: (address, suffix) ->
+    suffix or= ""
+    padding = "00000000".substring(0, 8 - String(address).length)
+    "#{@directory}/segment#{padding}#{address}#{suffix}"
+
+  # Move a replacement page file into place. Unlink the existing page file, then
+  # rename the new page file to the permanent name of the page file.
+  relink: (page, _) ->
+    replacement = @filename(page.address, ".new")
+    stat = fs.stat replacement, _
+    if not stat.isFile()
+      throw new Error "not a file"
+    permanent = @filename(page.address)
+    try
+      fs.unlink permanent, _
+    catch e
+      throw e unless e.code is "ENOENT"
+    fs.rename replacement, permanent, _
+
+  # We create a new branch pages in memory. They do not exist on disk until they
+  # are first written.
+  #
+  # A new branch page is given the next unused page number.
+  #
+  # In memory, a branch page is an array of child page addresses. It keeps track
+  # of its key and whether or not it is a penultimate branch page. The cache is
+  # used to cache the keys associated with the child page addresses. The cache
+  # maps the address of a child page to a key extracted from the first record of
+  # the leaf page referenced by the child page address.
+  #
+  # Our in memory is also cached and added as a node an MRU list. We must make
+  # sure that each page has only one in memory representation, because the in
+  # memory page is used for locking.
+
+  #
+  allocateBranches: (penultimate) ->
+    address = @nextAddress++
+    link = @link({ penultimate, address, addresses: [], cache: {} })
+    @cache[address] = link
+
+  # We write the branch page to a file as a single JSON object on a single line.
+  # We tuck the page properties into an object, and then serialize that object.
+  # We do not store the branch page keys. They are looked up as needed as
+  # described in the b-tree overview above.
+  #
+  # We always write a page branch first to a replacement file, then move it
+  # until place using `relink`.
+
+  #
+  rewriteBranches: (page, _) ->
+    link unlink page
+    filename = @filename page.address, ".new"
+    record = [ page.penultimate, page.next?.address, page.addresses ]
+    json = JSON.stringify(record) + "\n"
+    fs.writeFile filename, json, "utf8", _
+
+  # To read a branch page we read the entire page and evaluate it as JSON. We
+  # did not store the branch page keys. They are looked up as needed as
+  # described in the b-tree overview above.
+
+  #
+  readBranches: (page, _) ->
+    link unlink page
+    filename = @filename page.address
+    json = fs.readFile filename, "utf8", _
+    [ penultimate, next, addresses ] = JSON.parse json
+    extend page, { penultimate, next, addresses }
+
+  # After creating a database object, the client will either open the existing
+  # database, or create a new database.
+
   # Create the database detroying any exisiting database.

   # —
@@ -805,6 +435,32 @@

   # TODO Very soon.
   close: (_) ->
+#
+#
+# A branch page file contains a single object that includes the array of page
+# addresses that reference the branch page's children.
+#
+# The branch page file also stores a penultimate flag, and the address of the
+# next sibling of the branch
+# page.
+# The keys associated with the child addresses are not stored in the
+# branch page.
+#
+# Put another way, each branch element in a branch page represents a leaf page.
+# The leaf pages are sorted by the tree collation. The object value used to sort
+# the branch page is first element in the sorted leaf page.
+#
+# TODO Okay, what? I'm forgetting that I need the addresses to inner tiers as
+# well as to leaf tiers, both, when the tier is not penultimate. Descending is
+# not going to work, that is going to load the entire tree into memory quickly.
+#
+# Hmm... Maybe not. Tree four deep means loading only a particular path. It
+# ensures that we read lock our way down the tree to get our record. Although, I
+# believe I thought long and hard about this, and jumping down to the leaves
+# wasn't a problem.
+#
+# We only need to store the sorted array of the leaf tier addresses in our
+# branch tier, since we can obtain the object used to collate the address

   # Link tier to the head of the MRU list.
   link: (entry) ->
@@ -821,18 +477,6 @@
     next.previous = previous
     previous.next = next
     entry
-
-  # Generate a file name for a tier file with an optional suffix for temporary
-  # rewrite files. Tier file names are generated from the tier address.
-  filename: (address, suffix) ->
-    suffix or= ""
-    padding = "00000000".substring(0, 8 - String(address).length)
-    "#{@directory}/segment#{padding}#{address}#{suffix}"
-
-  allocateBranches: (penultimate) ->
-    address = @nextAddress++
-    tier = @link({ penultimate, address, addresses: [], cache: {} })
-    @cache[address] = tier

   allocateLeaves: (addresses, right) ->
     address = @nextAddress++
@@ -881,24 +525,6 @@
         # exclusive and shared locks to clear.
         key = tierOrAddress.cache[address] = @key address, 0, _
       key
-
-  # Write the branches to a file as a JSON string. We tuck the tier properties
-  # into an object before we serialize it, so that it is easy deserialize.
-  writeBranches: (tier, suffix, _) ->
-    filename = @filename tier.address, suffix
-    record = [ tier.penultimate, tier.next?.address, tier.addresses ]
-    json = JSON.stringify(record) + "\n"
-    fs.writeFile filename, json, "utf8", _
-
-  # Read branches from a brach tier file and assign the tier properties to the
-  # given tier object.
-  #
-  # TODO: Move link to head always, to maintain MRU.
-  readBranches: (tier, _) ->
-    filename = @filename tier.address
-    json = fs.readFile filename, "utf8", _
-    [ penultimate, next, addresses ] = JSON.parse json
-    extend tier, { penultimate, next, addresses }

   # TODO Consider: can't I always just rewrite and link?
   rewriteBranches: (tier, _) ->
@@ -978,21 +604,6 @@
     io = new LeafIO @filename tier.address
     tier = extend tier, { addresses, cache, loaded, io }

-  # Move a new branch tier file into place. Unlink the existing branch tier
-  # file, then rename the new branch tier file to the permanent name of the
-  # branch tier file.
-  relink: (tier, _) ->
-    replacement = @filename(tier.address, ".new")
-    stat = fs.stat replacement, _
-    if not stat.isFile()
-      throw new Error "not a file"
-    permanent = @filename(tier.address)
-    try
-      fs.unlink permanent, _
-    catch e
-      throw e unless e.code is "ENOENT"
-    fs.rename replacement, permanent, _
-
   # TODO Move down, bring read and write up.
   lock: (exclusive, address, leaf, callback) ->
     if not tier = @cache[address]
@@ -1042,6 +653,448 @@
     @release tier
     @lock true, tier.address, false, _

+# Glossary:
+#
+#  * Descent - What we call an attempt to move from a parent node to a child
+#              node in  the tree, which may be paused because it encounters a
+#              lock on a tier.
+#  * Record - A JSON object stored in the b-tree.
+#  * Object - JavaScript objects are referred to as objects in this
+#             documentation and are not to be confused with actual records.
+
+#
+class Mutation
+  # The constructor always felt like a dangerous place to be doing anything
+  # meaningful in C++ or Java.
+  constructor: (@strata, @object, @key, @operation) ->
+    @io         = @strata._io
+    @options    = @strata._options
+
+    @parent     = { operations: [], locks: [] }
+    @child      = { operations: [], locks: [] }
+    @exclusive  = []
+    @shared     = []
+    @pivots     = []
+
+    { @extractor, @comparator } = @options
+
+    @key        = @extractor @object if @object? and not key?
+
+  # Descend the tree, optionally starting an exclusive descent. Once the descent
+  # is exclusive, all subsequent descents are exclusive.
+
+  # `mutation.descend(exclusive)`
+  descend: (exclusive) ->
+    # Add a new level to the plan.
+    @plan.unshift { operations: [], addresses: [] }
+
+    # Two or more exclusive levels mean that the parent is exclusive.
+    if @exclusive.length < 1
+      @strata._release @parent
+
+    # Any items in the exclusive level array means we are now locking exclusive.
+    exclusive or= @exclusive.length > 0
+
+    # Make child a parent.
+    @parent = @child
+    @child = { operations: [], locks: [], exclusive }
+
+    # Add the new child to the list of exclusive levels.
+    if exclusive
+      @exclusive.push(@child)
+
+  # Search for a value in a tier, returning the  index of the value or else
+  # where it should be inserted.
+  #
+  # There is some magic here, long forgotten at the time of documentation. The
+  # tree beneath each branch in an inner tier contains records whose value is
+  # equal to or greater than the pivot. Thus, the pivot of the first record on
+  # an inner tier is null, indicating that that is the branch for all values
+  # less than the value of the first branch with a real pivot.
+  #
+  # TODO: Reading through the code, this does not take this into account, and
+  # will problably send items that belong in the least value tree into the three
+  # that follows it. I'll clean this up with unit testing.
+
+  # `mutation.find(tier, key, callback)`
+  find: (tier, key, _) ->
+    size = tier.addresses.length
+    if tier.leaf
+      [ low, high, leaf ] = [ 0, size - 1, true ]
+    else if size is 1
+      return 0
+    else
+      [ low, high ] = [ 1, size - 1 ]
+    { comparator, io } = @
+    loop
+      mid = (low + high) >>> 1
+      compare = comparator key, io.key(tier, mid, _)
+      if compare is 0
+        break
+      if compare > 0
+        low = mid + 1
+      else
+        high = mid - 1
+    if compare is 0
+      if leaf
+        while mid != 0 && comparator(key, io.key(tier, mid - 1, _)) == 0
+          mid--
+      mid
+    else if branches
+      mid - 1
+    else
+      mid
+
+  hasKey: (tier, _) ->
+    for key in @operation.keys or []
+      die { key }
+      branch = @find(tier, key, _)
+      if tier.cache[branch] is key
+        return true
+    false
+
+  # TODO: We are always allowed to get a shared lock on any leaf page and read a
+  # value, as long as we let go of it immediately. This allows us to read from
+  # leaf pages to get branch values.
+  # TODO: Rename this descend.
+
+  # `mutation.mutate(callback)`
+  mutate: (_) ->
+    parent = null
+
+    @shared.push child = @io.lock false, 0, false, _
+    if @operation.keys and (child.addresses.length is 1 or @hasKey(child, _))
+      @exclusive.push @io.upgrade @shared.pop(), _
+
+    # TODO: Flag for go left.
+    while not child.penultimate or child.address is @soughtAddress
+      process.exit 1
+
+    # TODO: Flag for shared or exclusive. If exclusive, leave parent locked, if
+    # shared, then unlock parent.
+
+    mutation = @[@operation.method].call this, parent, child, _
+
+    @io.release tier for tier in @shared
+    @io.release tier for tier in @exclusive
+
+    mutation.mutate _ if mutation
+
+  insertSorted: (parent, child, _) ->
+    branch = @find child, @key, _
+    @exclusive.push leaves = @io.lock true, child.addresses[branch], true, _
+
+    addresses = leaves.addresses
+
+    # Insert the object value sorted.
+    for i in [0...addresses.length]
+      key = @io.key(leaves, i, _)
+      if @comparator(@key, key) <= 0
+        break
+
+    address = leaves.io.writeInsert @object, i, _
+    addresses.splice i, 0, address
+
+    if addresses.length > @options.leafSize and not @homogenous(leaves, _)
+      keys = []
+      process.nextTick _
+      # Opportunity to deadlock.
+      keys.push key = @io.key leaves, 0, _
+      if leaves.right
+        keys.push @io.key leaves.right, 0, _
+      operation =
+        method: "splitLeaf"
+        keys: keys
+      new Mutation(@strata, null, key, operation)
+
+  # If the leaf tier is now at the maximum size, we test to see if the leaf tier
+  # is filled with an identical key value, and if not, we split the leaf tier.
+  homogenous: (tier, _) ->
+    { io, comparator } = @
+    first = io.key(tier, 0, _)
+    last = io.key(tier, tier.addresses.length - 1, _)
+    comparator(first, last) is 0
+
+  get: (parent, child, _) ->
+    branch = @find child, @key, _
+    @shared.push leaves = @io.lock false, child.addresses[branch], true, _
+    address = @find leaves, @key, _
+    leaves.cache[leaves.addresses[address]]
+
+  splitLeaf: (parent, child, _) ->
+    branch = @find child, @key, _
+    @exclusive.push leaves = @io.lock true, child.addresses[branch], true, _
+
+    # See that nothing has changed since we last descended.
+    { comparator: c, io, options: { leafSize: length } } = @
+    return if c(io.key(leaves, 0, _), @key) isnt 0
+    return if @homogenous(leaves, _)
+    return if leaves.addresses.length < @options.leafSize
+
+    # We might have let things go for so long, that we're going to have to split
+    # the page into more than two pages.
+    partitions = Math.floor leaves.addresses.length / @options.leafSize
+    while (partitions * @options.leafSize) <= leaves.addresses.length
+      # Find a partition.
+      mid = Math.floor leaves.addresses.length / (partitions + 1)
+      key = io.key leaves, mid, _
+      [ before, after ] = [ mid - 1, mid + 1 ]
+      while not partition
+        if before > 0 and c(io.key(leaves, before, _), key) isnt 0
+          partition = before + 1
+        else if after <= length and c(io.key(leaves, after, _)) isnt 0
+          partition = after
+        else
+          --before
+          ++after
+
+      key = io.key leaves, partition, _
+      pivot = @find(child, key, _) + 1
+
+      addresses = leaves.addresses.splice(partition)
+      right = io.allocateLeaves(addresses, leaves.right)
+
+      leaves.right = right.address
+
+      child.addresses.splice(pivot, 0, right.address)
+
+      # Append an operation indciator. This would be a record that describes all
+      # the participants in the rewrite, in the order in which they are supposed
+      # to be rewritten. When found, we look at the last item in the list, then
+      # load that, and ensure that the last record is a complete operation
+      # record. If it is, we know that all of the temporary pages were written.
+      @io.rewriteLeaves(leaves, _)
+      @io.rewriteLeaves(right, _)
+
+      @io.rewriteBranches(child, _)
+
+      @io.relink(right, _)
+      @io.relink(leaves, _)
+      @io.relink(child, _)
+
+      --paritions
+
+  mergeLeaf: (parent, child, _) ->
+    [ key, rightKey ] = @operation.keys
+    compare = @comparator(full, leftKey = @io.key(child, 0, _))
+    if compare is 0
+      return if leftKey isnt key
+
+class Level
+  # Construct a new level. The `exclusive` pararameter determines how this level
+  # locks tiers when asked to lock tiers.
+  constructor: (@exclusive) ->
+    @operations = []
+    @locks = []
+
+  # Convert lock to read lock and tell anyone waiting that they can go ahead.
+  #
+  # We give the next tick a copy of just the continuations to fire, excluding
+  # our own continuation which we're about to add. (TODO rename continuations?)
+  #
+  # If you're wondering, no, you're not going to fire the continuations twice.
+  # They are only ever fired by the decent that holds the exclusive lock. This
+  # level will be removed from the level list, so we won't grab at it when the
+  # operations are over.
+  downgrade: (strata) ->
+    if @exclusive
+      while @locks.length
+        lock = @locks.shift()
+        locks = strata.locks[lock.address]
+        throw "not locked by me" if locks[0][0] isnt lock
+        locks.shift()
+        @_resume strata, locks[0].slice(0)
+        locks[0].push lock
+      @exclusive = false
+
+  # Unused.
+  advance: (strata) ->
+    locks = strata.locks[@tier.id]
+    if locks[0].length is 0 and locks.length isnt 1
+      locks.shift()
+      true
+    false
+
+# ### B-Tree Structure
+#
+# We call page of the b-tree, ah, maybe we stop using the word tier, start using
+# the word page, because we're not going to have to reduce the ambiguity with
+# some sort of subdivided binary file structure.
+#
+# NOTE Come back and look at this. It is succinct.
+#
+# There are two types of pages. Branch pages and leaf pages.
+#
+# Leaf pages store the records. They maintain the collation
+#
+# Leaf pages are all uniformly the same.
+#
+# ### Leaf Tier Files
+#
+# A leaf tier maintains an array of file positions called an address array. A
+# file position in the address array references a record in the leaf tier file
+# by its file position. The addresses in the address array are sorted by the
+# the sort order of the referenced records according to the sort order of the
+# b-tree.
+#
+# A leaf tier file contains JSON objects, one object on each line.
+#
+# There are three types of objects in a leaf tier file, insert objects, delete
+# objects, and address array objects.
+#
+# An insert object contains a *record* and the index in the address array where
+# the record's address would be inserted to preserve the sort order of the
+# address array.
+#
+# Beginning with an empty array and reading from the start of the file, the leaf
+# tier address array is reconstituted by reading the object at the current
+# position in the file, then inserting the position of the current object into
+# the address array at the index in the insert object.
+#
+# We record a deletion by appending a delete object. A delete object contains
+# an index in the address array that should be deleted. When reading the leaf
+# tier from the start of the file, we insert an address into the address array
+# when we encounter an insert object, and we delete an address when we encounter
+# a delete object.
+#
+# On occasion, we can store an address array object, An address array object
+# contains the address array itself.  We store a copy of a constructed address
+# array object in the leaf tier file so that we can read a large leaf tier file
+# quickly.
+#
+# When we read a leaf tier file, if we read from the back of the file toward the
+# front, we can read backward until we find an address array object. Then we can
+# read forward to the end of the file, applying the inserts and deletes that
+# occured after we wrote the address array object.
+#
+# When a leaf tier file is large, stashing the constructed address array at the
+# end means that the leaf tier can be loaded quickly, because we will only have
+# to read backwards a few entries to find a mostly completed address array. We
+# can then read forward from the array to amend the address array with the
+# inserts and deletes that occured after it was written.
+#
+# Not all of the records will be loaded when we go backwards, but we have their
+# file position from the address array, so we can jump to them and load them as
+# we need them. That is, if we need them, because owing to binary search, we
+# might only need a few records out of a great many records to find the record
+# we're looking for.
+#
+# Over time, a leaf tier file can grow fat with deleted records — an
+# insert object and a subsequent delete object. We vacuum a leaf tier file by
+# writing it to a new leaf tier file.
+
+# `LeafIO` — Keeps track of a file descriptor opened for append, and the
+# append file position. The `LeafIO` class does *not* contain all methods
+# relating to leaf tier file I/O. You can find more leaf tier file methods in
+# the `IO` class below.
+#
+# TODO Rename `RecordIO`.
+class LeafIO
+  # Construct a leaf file tier writer for the given file name.
+  constructor: (@filename) ->
+    @length = 1024
+
+  # #### Leaf Tier File Object Format
+  #
+  # Each leaf tier object is a JSON array. The first element of the array is an
+  # integer.
+  #
+  # If the integer is greater than zero, it indicates an insert object.  The
+  # integer is the one based index into the zero based address array, indicating
+  # the index where the position of the current insert object should be
+  # inserted. The second element of the leaf tier object array is the record
+  # object.
+
+  # Write an insert object.
+  writeInsert: (object, index, _) ->
+    @_writeJSON [ index + 1, object ], _
+
+  # If the integer is less than zero, it indicates a delete object. The absolute
+  # value of the integer is the one based index into the zero based addrss
+  # array, indicating the index of address array element that should be deleted.
+  # There are no other elements in the leaf tier object array.
+
+  # Write a delete object.
+  writeDelete: (index, _) ->
+    @_writeJSON [ -(index + 1) ], _
+
+  # If the integer is zero, it indicates an address array object. The integer
+  # value of zero is used only as an indicator. The second element is the
+  # address of the leaf tier to the right of current leaf tier. The third
+  # element of the leaf tier object array is the address array.
+
+  # Write an address array object.
+  writeAddresses: (right, addresses, _) ->
+    @_writeJSON [ 0, right, addresses ], _
+
+  # Append an object to the leaf tier file as a single line of JSON.
+  _writeJSON: (object, _) ->
+    @fd           or= fs.open @filename, "a+", 0644, _
+    @position     or= fs.stat(@filename, _).size
+
+    json            = JSON.stringify object
+    position        = @position
+    length          = Buffer.byteLength(json, "utf8")
+    buffer          = new Buffer(length + 1)
+    offset          = 0
+
+    # Write JSON and newline.
+    buffer.write json
+    buffer[length] = 0x0A
+
+    # Write may be interrupted by a signal, so we keep track of how many bytes
+    # are actually written and write the difference if we come up short.
+    while offset != buffer.length
+      count = buffer.length - offset
+      written = fs.write @fd, buffer, offset, count, @position, _
+      @position += written
+      offset += written
+
+    # What's the point if it doesn't make it to disk?
+    fs.fsync @fd, _
+
+    # Return the file position of the inserted record.
+    position
+
+  # Each line is terminated by a newline. In case your concerned that this
+  # simple search will mistake a byte inside a multi-byte character for a
+  # newline, have a look at
+  # [UTF-8](http://en.wikipedia.org/wiki/UTF-8#Description). All bytes
+  # participating in a multi-byte character have their leading bit set, all
+  # single-byte characters have their leading bit unset.
+
+  # Search for the newline that separates JSON records.
+  _readJSON: (buffer, read) ->
+    for offset in [0...read]
+      if buffer[offset] is 0x0A
+        return JSON.parse buffer.toString("utf8", 0, offset + 1)
+
+  # Jump to a position in the file and read a specific object. Because objects
+  # are cached in memory by a leaf tier, we're not going to get a request to
+  # read an object that has been written to the current write stream. When we
+  # read in a leaf tier, we'll cache any objects we encounter prior to read the
+  # ordering array.
+  #
+  # Note how we allow we keep track of the minimum buffer size that will
+  # accommodate the largest possible buffer.
+  readObject: (address, _) ->
+    @fd or= fs.open @filename, "a+", _
+    @position or= fs.stat(@filename, _).size
+    loop
+      buffer = new Buffer(@length)
+      read = fs.read @in, buffer, 0, buffer.length, address, _
+      if json = @_readJSON(buffer, read)
+        break
+      if @length > @position - address
+        throw new Error "cannot find end of record."
+      @length += @length >>> 1
+    json.pop()
+
+  # Close the file descriptor if it is open and reset the file descriptor and
+  # append position.
+  close: (_) ->
+    fs.close(@fd, _) if @fd
+    @position = @fd = null
 class exports.Strata
   # Construct the Strata from the options.
   constructor: (options) ->


Fixes function datasources

Fixes function datasources

--- src/client/datasource.coffee
+++ src/client/datasource.coffee
@@ -55,8 +55,10 @@
       dfd = $.Deferred()
       if _.isFunction(@def)
         ret = @def()
-        dfd = ret.deferred if ret?.deferred
-        dfd.resolve ret
+        if ret?.promise
+          dfd = ret
+        else
+          dfd.resolve ret
       else
         dfd.resolve(false) if /undefined/.test(@def.path)
         if @type == 'model'


added mechanism to blur things on events. Also state property on tools

--- src/coffee/directives.coffee
+++ src/coffee/directives.coffee
@@ -1,6 +1,14 @@
 require ['angular', 'lodash', 'lines', 'jschannel', 'services'], (ng, L, lines, Channel) ->

   Directives = ng.module('steps.directives', ['steps.services'])
+
+  Directives.directive 'blurOn', ->
+    restrict: 'A'
+    scope:
+      blurOn: '@'
+    link: (scope, element) ->
+      el = element[0]
+      element.on scope.blurOn, el.blur.bind(el)

   # Pair of recursive directives for turning any json structure into an editable form.
   Directives.directive 'editableItem', ($compile) ->
@@ -215,6 +223,7 @@
       tool:     '='
       step:     '='
       fullSize: '='
+      state:    '='
       onToggle: '&'
       hasItems: '&'
       has:      '&'


better equality comparison for values

better equality comparison for values

This test would fail for array values, etc.

--- bokehjs/src/coffee/common/client.coffee
+++ bokehjs/src/coffee/common/client.coffee
@@ -390,7 +390,7 @@
           if event.new_ instanceof HasProperties
             if typeof patch_new == 'object' and 'id' of patch_new and patch_new['id'] == event.new_.id
               return true
-          else if patch_new == event.new_
+          else if _.isEqual(patch_new, event.new_)
             return true
     else if event instanceof RootAddedEvent
         for event_json in patch.content['events']


fix: grammar highlight (#78)

fix: grammar highlight (#78)

--- grammars/coursescript.cson
+++ grammars/coursescript.cson
@@ -82,7 +82,9 @@
   # QuestionPart
   {
     'match': '(ID|Text|Audio|Pic|Video|VideoClip|Opts|TR_TEXT|TR_Text|Title|Mix|Scene|QuestionPart|coordinate):\\S'
-    'name': 'storage.type.coursescript'
+    'captures': {
+      1: { 'name': 'storage.type.coursescript' }
+    }
   }
   # DF01
   {


Disable DirectWrite, fixes #3540

Disable DirectWrite, fixes #3540

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -28,6 +28,7 @@
       title: 'Atom'
       icon: @constructor.iconPath
       'web-preferences':
+        'direct-write': false
         'subpixel-font-scaling': false
     global.atomApplication.addWindow(this)


remove unused

remove unused

--- chrome-app/filters/replaceImages.coffee
+++ chrome-app/filters/replaceImages.coffee
@@ -1,19 +0,0 @@
-# Created by frank on 14-6-17.
-app = require '../app'
-
-module.exports = app.filter 'ffReplaceImages', ($sce)->
-  (input)->
-    return '' if not input
-    wrapper = document.createElement 'div'
-    wrapper.innerHTML = input
-    images = wrapper.querySelectorAll 'img'
-
-    for image in images
-      if (image.src.indexOf 'javascript:') is 0
-        image.remove
-      else
-        image.setAttribute 'data-src',image.src
-        image.src = 'assets/svg/loading-spinning-bubbles.svg'
-    input = wrapper.innerHTML
-    console.log 'ffRI'
-    $sce.trustAsHtml input


Minor edits to client-scripts

Minor edits to client-scripts

--- gulp/client-scripts.coffee
+++ gulp/client-scripts.coffee
@@ -7,8 +7,8 @@
 # Copyright  2015 Tranzit Development Team

 #
-# The purpose of this file is to allow all client scripts to be
-# easily checked for syntax and logic errors as well as bundled
+# The purpose of this file is to allow all client logic scripts to be
+# to be easily checked for syntax and logic errors as well as bundled
 # together with all necessary libraries and frameworks
 #
 # rosenbea


Use braoder null check

Use braoder null check

--- src/coffee/helpers/sftp.coffee
+++ src/coffee/helpers/sftp.coffee
@@ -19,7 +19,7 @@
   listFiles: (sftp, dirName) ->
     new Promise (resolve, reject) ->
       sftp.readdir dirName, (err, list) ->
-        if err?
+        if err
           reject err
         else
           resolve list


Adjust doc.

Adjust doc.

--- src/coffee/orderservice.coffee
+++ src/coffee/orderservice.coffee
@@ -11,7 +11,7 @@
     @client = options.sphere_client

   ###
-  Returns only orders without given syncInfo channel.
+  Returns only orders which haven't been synced using the given channel.
   @param {Collection} orders List of order resources.
   @param {Channel} channel SyncInfo channel to which an order mustn't have.
   @return {Collection}


remove mails app from initialization

remove mails app from initialization

--- init.coffee
+++ init.coffee
@@ -29,17 +29,17 @@
         description: """
         Get Things Done
         """
-    new Application
-        name: "Emails"
-        state: "installed"
-        index: 0
-        slug: "mails"
-        icon: "mails_icon.png"
-        port: 8003
-        git: "git://github.com/mycozycloud/cozy-mails.git"
-        description: """
-        Manage your emails
-        """
+    #new Application
+        #name: "Emails"
+        #state: "installed"
+        #index: 0
+        #slug: "mails"
+        #icon: "mails_icon.png"
+        #port: 8003
+        #git: "git://github.com/mycozycloud/cozy-mails.git"
+        #description: """
+        #Manage your emails
+        #"""
     ]

 saveFunc = (app) ->


update init to fit new data tree

update init to fit new data tree

--- init.coffee
+++ init.coffee
@@ -1,15 +1,13 @@
 server = require './server'
 async = require "async"

-DataTree = require("./common/tree/tree_object").Tree
+DataTree = require("./common/data-tree").DataTree

 ## Small script to intialize list of available applications.

 todolist = new TodoList
     title: "Tutorial"
-    path: "/all/tutorial"
-    humanPath: "All,Tutorial"
-    tags: "all,tutorial"
+    path: ["All","Tutorial"]

 tasks = [
@@ -65,8 +63,8 @@
                 console.log err
                 console.log "Initialization failed (cannot update tree)"
                 process.exit(0)
-            dataTree = new DataTree JSON.parse(tree.struct)
-            dataTree.addNode "/all", list.title, list.id
+            dataTree = new DataTree tree.struct
+            dataTree.addNode list

             tree.updateAttributes struct: dataTree.toJson(), (err) ->
                 console.log "Initialization succeeds."


remove log

remove log

--- src/core/actorFactory.coffee
+++ src/core/actorFactory.coffee
@@ -39,7 +39,6 @@
       interceptor:interceptor
       route:@mapRoute(options.routes)
     })
-    console.log(interceptors)
     interceptor
 module.exports ={
   actorFactory:new ActorFactory({


Flexible dispatcher button arguments

Flexible dispatcher button arguments

--- src/dispatcher-button.coffee
+++ src/dispatcher-button.coffee
@@ -3,14 +3,16 @@
   mixins: [mixin]
   propTypes:
     event: React.PropTypes.any.isRequired
-    args: React.PropTypes.any.isRequired
+    args: React.PropTypes.any
+    className: React.PropTypes.string
   render: ->
     rootElement =
       if (typeof @props.children) is 'string'
         'button'
       else
         'span'
+    args = @props.args ? []
     React.createElement rootElement, {
       className: @props.className ? 'arda-dispatcher-button'
-      onClick: => @dispatch @props.event, @props.args...
+      onClick: => @dispatch @props.event, args...
     }, @props.children


Reload member when saving emails or phones

--- src/persistence.coffee
+++ src/persistence.coffee
@@ -237,9 +237,74 @@
       toRemove.push contact.contactPhoneNumbers...

       linking.unlinkItems toRemove, lookup
-      deleteContact.call(this, contact, callback).fail((err) ->
+      deleteContact.call(this, contact, callback).then((result) ->
+        sdk.loadMembers({memberId: contact.memberId}).then -> result
+      ).fail((err) ->
         linking.linkItems toRemove, lookup
         err
+      ).callback callback
+
+  # Reload member when saving memberEmailAddress
+  wrapMethod sdk, 'saveMemberEmailAddress', (saveMemberEmailAddress) ->
+    (emailAddress, callback) ->
+      saveMemberEmailAddress.call(this, emailAddress, callback).then((result) ->
+        sdk.loadMembers({id: emailAddress.memberId}).then -> result
+      ).callback callback
+
+  # Reload member when deleting memberEmailAddress
+  wrapMethod sdk, 'deleteMemberEmailAddress', (deleteMemberEmailAddress) ->
+    (emailAddress, callback) ->
+      deleteMemberEmailAddress.call(this, emailAddress, callback)
+      .then((result) ->
+        sdk.loadMembers({id: emailAddress.memberId}).then -> result
+      ).callback callback
+
+  # Reload member when saving memberPhoneNumber
+  wrapMethod sdk, 'saveMemberPhoneNumber', (saveMemberPhoneNumber) ->
+    (phoneNumber, callback) ->
+      saveMemberPhoneNumber.call(this, phoneNumber, callback)
+      .then((result) ->
+        sdk.loadMembers({id: phoneNumber.memberId}).then -> result
+      ).callback callback
+
+  # Reload member when deleting memberPhoneNumber
+  wrapMethod sdk, 'deleteMemberPhoneNumber', (deleteMemberPhoneNumber) ->
+    (phoneNumber, callback) ->
+      deleteMemberPhoneNumber.call(this, phoneNumber, callback)
+      .then((result) ->
+        sdk.loadMembers({id: phoneNumber.memberId}).then -> result
+      ).callback callback
+
+  # Reload member when saving contactEmailAddress
+  wrapMethod sdk, 'saveContactEmailAddress', (saveContactEmailAddress) ->
+    (emailAddress, callback) ->
+      saveContactEmailAddress.call(this, emailAddress, callback)
+      .then((result) ->
+        sdk.loadMembers({id: emailAddress.memberId}).then -> result
+      ).callback callback
+
+  # Reload member when deleting contactEmailAddress
+  wrapMethod sdk, 'deleteContactEmailAddress', (deleteContactEmailAddress) ->
+    (emailAddress, callback) ->
+      deleteContactEmailAddress.call(this, emailAddress, callback)
+      .then((result) ->
+        sdk.loadMembers({id: emailAddress.memberId}).then -> result
+      ).callback callback
+
+  # Reload member when saving contactPhoneNumber
+  wrapMethod sdk, 'saveContactPhoneNumber', (saveContactPhoneNumber) ->
+    (phoneNumber, callback) ->
+      saveContactPhoneNumber.call(this, phoneNumber, callback)
+      .then((result) ->
+        sdk.loadMembers({id: phoneNumber.memberId}).then -> result
+      ).callback callback
+
+  # Reload member when deleting contactPhoneNumber
+  wrapMethod sdk, 'deleteContactPhoneNumber', (deleteContactPhoneNumber) ->
+    (phoneNumber, callback) ->
+      deleteContactPhoneNumber.call(this, phoneNumber, callback)
+      .then((result) ->
+        sdk.loadMembers({id: phoneNumber.memberId}).then -> result
       ).callback callback

   # Load availabilities for the new event


fix (focus and lostFocus events) add eventHandlerId in the API call

--- src/Events.coffee
+++ src/Events.coffee
@@ -1,6 +1,8 @@

 class Events
+
+  @eventCounter = Date.now()

   @dispatchVisibilitychangedEvent: (options={}) =>
     steroids.debug
@@ -51,10 +53,14 @@
       steroids.debug
         msg: "focus added"

+      event = "lostFocus"
+      eventHandlerId = ++Events.eventCounter;
+
       steroids.nativeBridge.nativeCall
         method: "addEventListener"
         parameters:
-          event: "lostFocus"
+          event: event
+          eventHandlerId: "#{event}_#{eventHandlerId}"
         successCallbacks: [lostFocusAdded,callbacks.onSuccess]
         recurringCallbacks: [becomeHiddenEvent, callbacks.onFailure]

@@ -81,9 +87,13 @@
       @dispatchVisibilitychangedEvent()

+    event = "focus"
+    eventHandlerId = ++Events.eventCounter;
+
     steroids.nativeBridge.nativeCall
       method: "addEventListener"
       parameters:
-        event: "focus"
+        event: event
+        eventHandlerId: "#{event}_#{eventHandlerId}"
       successCallbacks: [focusAdded,callbacks.onSuccess]
-      recurringCallbacks: [becomeVisibleEvent, callbacks.onFailure]+      recurringCallbacks: [becomeVisibleEvent, callbacks.onFailure]


Woops, fix drag-and-drop uploads

Woops, fix drag-and-drop uploads

--- app/assets/javascripts/upload.coffee
+++ app/assets/javascripts/upload.coffee
@@ -15,7 +15,7 @@
     formData = new FormData()
     xhr = new XMLHttpRequest()
     formData.append "file", file
-    xhr.open "POST", "/api/v1/runs", true
+    xhr.open "POST", "/upload", true
     xhr.onload = ->
       if xhr.status is 200
         console.log "all done: " + xhr.status


GitProvider: instead of importing based on the url, take action based on the requested provider

--- workers/social/lib/social/models/gitprovider/index.coffee
+++ workers/social/lib/social/models/gitprovider/index.coffee
@@ -37,13 +37,22 @@
       shouldReviveProvider : no
     }, (client, importParams, callback) ->

-      { user }  = client.r
-      providers = [ GitHubProvider, GitLabProvider ]
+      providers =
+        gitlab  : GitLabProvider
+        github  : GitHubProvider

-      for provider in providers
-        return  if provider.importStackTemplateData importParams, user, callback
+      unless _provider = importParams.provider
+        return callback new KodingError 'Unknown provider'

-      callback new KodingError 'Invalid url'
+      provider = providers[_provider]
+
+      unless provider
+        return callback new KodingError 'Provider is not supported'
+
+      { user } = client.r
+
+      unless provider.importStackTemplateData importParams, user, callback
+        callback new KodingError 'Invalid url'

   @createImportedStackTemplate = permit 'import stack template',


mongo imports debug limit switched off

mongo imports debug limit switched off

--- data/imports/aiddata.coffee
+++ data/imports/aiddata.coffee
@@ -11,7 +11,7 @@
 pg = require 'pg'
 pgurl = dbconf.postgres

-DEBUG_updateRowsLimit = 10   # set for debugging
+DEBUG_updateRowsLimit = null  # set for debugging
 OMIT_NULL_VALUE_FIELDS_IN_COMMITMENTS_JSON = false

 PATH = "data/imports/"
@@ -35,7 +35,7 @@
                 "-u #{dbconf.mongodb.user} -p #{dbconf.mongodb.password} "+
                 "#{file}"

-    console.log "Running in \"#{__dirname}\":\n#{cmd}"
+    console.log "Running command:\n#{cmd}"
     os.run cmd,
       (err) ->
         callWhenEnded(err)


forgot to remove a console.log

forgot to remove a console.log

--- SingularityUI/app/thirdPartyConfigurations.coffee
+++ SingularityUI/app/thirdPartyConfigurations.coffee
@@ -12,8 +12,6 @@
     opts.xhrFields.withCredentials = true

     _oldAjax.call jQuery, opts
-
-console.log moment

 # Eat M/D/Y & 24h-time, yanks! Mwahahahahaha!
 moment.locale 'en',


try catch constraint removal

try catch constraint removal

--- src/Thread.coffee
+++ src/Thread.coffee
@@ -178,7 +178,8 @@
         try
           @solver.removeConstraint constraint
         catch error
-          throw new Error "cant remove constraint by tracker: #{tracker}"
+          #throw new Error "cant remove constraint by tracker: #{tracker}"
+          movealong = 'please'
       delete @constraintsByTracker[tracker]
     # clean up varcache
     if @varIdsByTracker[tracker]


Removed localhost from image paths

Removed localhost from image paths

--- gulp/development/webserver.coffee
+++ gulp/development/webserver.coffee
@@ -18,27 +18,27 @@
     feeds = [
       {
         caption: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
-        image: 'http://localhost:61337/images/dummy_00.png'
+        image: '/images/dummy_00.png'
         description: 'Vestibulum id ligula porta felis euismod semper. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.'
       },{
         caption: 'Aenean lacinia bibendum nulla sed consectetur.'
-        image: 'http://localhost:61337/images/dummy_01.png'
+        image: '/images/dummy_01.png'
         description: 'Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Sed posuere consectetur est at lobortis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.'
       },{
         caption: 'Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum ma.'
-        image: 'http://localhost:61337/images/dummy_02.png'
+        image: '/images/dummy_02.png'
         description: 'Aenean lacinia bibendum nulla sed consectetur. Vestibulum id ligula porta felis euismod semper. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Nulla vitae elit libero, a pharetra augue. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nulla vitae elit libero, a pharetra augue.'
       },{
         caption: 'Vestibulum id ligula porta felis euismod semper.'
-        image: 'http://localhost:61337/images/dummy_03.png'
+        image: '/images/dummy_03.png'
         description: 'Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nulla vitae elit libero, a pharetra augue. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Donec sed odio dui.'
       },{
         caption: 'Nullam quis risus eget urna mollis ornare vel eu leo.'
-        image: 'http://localhost:61337/images/dummy_04.png'
+        image: '/images/dummy_04.png'
         description: 'Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Etiam porta sem malesuada magna mollis euismod. Donec ullamcorper nulla non metus auctor fringilla. Aenean lacinia bibendum nulla sed consectetur. Nullam id dolor id nibh ultricies vehicula ut id elit.'
       },{
         caption: 'Sed posuere consectetur est at lobortis.'
-        image: 'http://localhost:61337/images/dummy_05.png'
+        image: '/images/dummy_05.png'
         description: 'Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Cras mattis consectetur purus sit amet fermentum. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Maecenas faucibus mollis interdum. Sed posuere consectetur est at lobortis.'
       }
     ]


removes irrelevant specs

removes irrelevant specs

--- apps/artist/test/client/works.coffee
+++ apps/artist/test/client/works.coffee
@@ -33,9 +33,4 @@
   describe '#render', ->
     it 'renders the template', ->
       @view.render()
-      @view.$('#artwork-section').length.should.equal 1
-
-  describe '#postRender', ->
-    it 'sets up artwork filter in infinite mode', ->
-      @view.render()
-      @artworkFilterInitStub.args[0][0].mode.should.equal 'infinite'
+      @view.$('#artwork-section').length.should.equal 1


[refactor] don't use KEYS in production, use SCAN

--- src/redis-model.coffee
+++ src/redis-model.coffee
@@ -99,16 +99,30 @@

     callback null, stuck

+  scanRedis: (match, callback) ->
+    ideally = errify callback
+    result  = []
+
+    nextindex = 0
+    while nextindex?
+      await @redis.scan nextindex, "MATCH", match, ideally defer nextindexAndItems
+      [nextindex, items] = nextindexAndItems
+      result             = result.concat items
+      nextindex          = parseInt nextindex
+      if nextindex is 0 then nextindex = null
+
+    callback null, result
+
   #Returns all JOB keys in string form (ex: bull:video transcoding:101)
   allKeys: (queue = "*", callback) ->
     ideally = errify callback

-    await @redis.keys "bull:#{queue}:[0-9]*", ideally defer jobsWithLocks
+    await @scanRedis "bull:#{queue}:[0-9]*", ideally defer jobsWithLocks
     result = (jobWithLock for jobWithLock in jobsWithLocks when jobWithLock[-5..] isnt ":lock")
     callback null, result

   listsByState: (queue = "*", state, callback) ->
-    @redis.keys "bull:#{queue}:#{state}", callback
+    await @scanRedis "bull:#{queue}:#{state}", callback

   #Returns the job data from a list of job ids
   fullKeysForList: (list, callback) ->
@@ -124,10 +138,12 @@
     return callback() unless ids and Array.isArray ids
     ideally = errify callback

-    multi = (["keys", "bull:*:#{id}"] for id in ids)
-    await (@redis.multi multi).exec ideally defer arrayOfArrays
-    result = (array[0] for array in arrayOfArrays when array.length is 1)
-    callback null, result
+    keys = []
+    for id in ids
+      await @scanRedis "bull:*:#{id}", ideally defer keymatches
+      keys = keys.concat keymatches
+
+    callback null, keys

   #Returns all jobs in object form, with state applied to object. Ex: {id: 101, queue: "video transcoding", state: "pending"}
   formatJobs: (queue = "*", keys, callback) ->
@@ -311,7 +327,7 @@
     ideally   = errify callback
     allCounts = []

-    await @redis.keys "bull:*:id", ideally defer queues
+    await @scanRedis "bull:*:id", ideally defer queues
     for queue, i in queues
       name = queue[5..-4]
       await @stateCounts name, ideally defer allCounts[i]


[fix] we also need keys for stuck jobs

[fix] we also need keys for stuck jobs

--- src/redis-model.coffee
+++ src/redis-model.coffee
@@ -15,7 +15,7 @@
     ideally = errify callback

     await @idsAndCountByState queue, state,  ideally defer list
-    await @fullKeysForList list,             ideally defer names unless state is "stuck"
+    await @fullKeysForList list,             ideally defer names
     await @formatJobs queue, names,          ideally defer jobs
     await @dataForJobs jobs,                 ideally defer jobs
     await @stacktraceForJobs jobs,           ideally defer jobs if state is "failed"


Fix remove selected tables button

Fix remove selected tables button

--- admin/static/coffee/namespaces/index.coffee
+++ admin/static/coffee/namespaces/index.coffee
@@ -2,7 +2,7 @@
 # This file extends the namespace module with functionality to present
 # the index of namespaces.
 module 'NamespaceView', ->
-    # Show a list of databases, this view extends AbstractList
+    # Show a list of databases
     class @DatabaseList extends UIComponents.AbstractList
         # Use a namespace-specific template for the namespace list
         template: Handlebars.compile $('#database_list-template').html()
@@ -18,20 +18,18 @@
         initialize: ->
             log_initial '(initializing) namespace list view'

-            # Create the modal we may need
             @add_database_dialog = new NamespaceView.AddDatabaseModal
             @add_namespace_dialog = new NamespaceView.AddNamespaceModal
             @remove_namespace_dialog = new NamespaceView.RemoveNamespaceModal

-
             super databases, NamespaceView.DatabaseListElement, '.collapsible-list', {}, 'database', 'cluster'

             @datacenters_length = -1
             @databases_length = -1
+            datacenters.on 'all', @update_button_create_namespace
             databases.on 'all', @update_button_create_namespace
             @can_create_namespace = true

-        # User cannot create a namespace if there is no database
         update_button_create_namespace: =>
             if databases.length is 0 and @can_create_namespace is true
                 @.$('.add-namespace').prop 'disabled', 'disabled'
@@ -95,10 +93,15 @@

         # Callback that will be registered: updates the toolbar buttons based on how many namespaces have been selected
         update_toolbar_buttons: =>
-            @.$('.btn.remove-namespace').is ':disabled', @get_selected_namespaces().length < 1
+            if @get_selected_namespaces().length < 1
+                @.$('.btn.remove-namespace').prop 'disabled', 'disabled'
+            else
+                @.$('.btn.remove-namespace').removeProp 'disabled'
+            #@.$('.btn.remove-namespace').is ':disabled', @get_selected_namespaces().length < 1

         destroy: =>
             super
+            datacenters.off 'all', @update_button_create_namespace
             databases.off 'all', @update_button_create_namespace
             @add_database_dialog.destroy()
             @add_namespace_dialog.destroy()


:art: Remove unnecessary comment

:art: Remove unnecessary comment

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -84,7 +84,6 @@
     @subscriptions.add(@suggestionList.onDidCancel(@hideSuggestionList))

   handleCommands: =>
-    # Allow autocomplete to be triggered via keymap
     @subscriptions.add atom.commands.add 'atom-text-editor',
       'autocomplete-plus:activate': =>
         @shouldDisplaySuggestions = true


:lipstick: Use position arguments for clarity

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -71,7 +71,7 @@
         matchPosition = @findMatchingStartPair(position, matchingPair, currentPair)

     if position? and matchPosition?
-      @moveHighlightViews([position, matchPosition])
+      @moveHighlightViews(position, matchPosition)
       @pairHighlighted = true

   findMatchingEndPair: (startPairPosition, startPair, endPair) ->
@@ -132,12 +132,11 @@
     element.style.width = "#{@editorView.charWidth}px"
     element.style.height = "#{@editorView.lineHeight}px"

-  moveHighlightViews: (bufferRange) ->
-    [start, end] = bufferRange
-    startPixelPosition = @editorView.pixelPositionForBufferPosition(start)
-    endPixelPosition = @editorView.pixelPositionForBufferPosition(end)
-    @moveHighlightView(@startView, start, startPixelPosition)
-    @moveHighlightView(@endView, end, endPixelPosition)
+  moveHighlightViews: (startBufferPosition, endBufferPosition) ->
+    startPixelPosition = @editorView.pixelPositionForBufferPosition(startBufferPosition)
+    endPixelPosition = @editorView.pixelPositionForBufferPosition(endBufferPosition)
+    @moveHighlightView(@startView, startBufferPosition, startPixelPosition)
+    @moveHighlightView(@endView, endBufferPosition, endPixelPosition)

   findCurrentPair: (matches) ->
     position = @editor.getCursorBufferPosition()


secret: fix set parameters

secret: fix set parameters

--- lib/commands/secrets/set.coffee
+++ lib/commands/secrets/set.coffee
@@ -7,7 +7,7 @@
   store.get (err, data) ->
     return callback err if err
     # Secret already set, need the overwrite option
-    [property, password] = params.property.split ' '
+    [property, password] = params.property
     password_generated = false
     value = get data, property
     if value and not params.overwrite


Update FileHandler.coffee

Update FileHandler.coffee

Restoring error check.

--- app/coffee/FileHandler.coffee
+++ app/coffee/FileHandler.coffee
@@ -12,6 +12,7 @@
 	insertFile: (bucket, key, stream, callback)->
 		convetedKey = KeyBuilder.getConvertedFolderKey(key)
 		PersistorManager.deleteDirectory bucket, convetedKey, (error) ->
+			return callback(error) if error?
 			PersistorManager.sendStream bucket, key, stream, callback

 	deleteFile: (bucket, key, callback)->


String Interpolation

String Interpolation

--- src/tasks/build.coffee
+++ src/tasks/build.coffee
@@ -21,20 +21,24 @@

 		flags = grunt.config.get('internal.flags')
 		data  = _.merge {}, (if config[flags.cms] then config[flags.cms] else config.default), flags
-		out   = path.out.dist+'/'+data.name+'_'+grunt.template.today('_yyyy.mm.dd-HH.MM.ss')
+		out   = "#{path.out.dist}/#{data.name}_#{grunt.template.today('_yyyy.mm.dd-HH.MM.ss')}"

-		data.root     += if data.theme then '/'+pkg.name else ''
-		data.build     = data.root+'/builds'
+		data.root     += if data.theme then "/#{pkg.name}" else ''
+		data.build     = "#{data.root}/builds"
 		data.package   = pkg.name
 		data.version   = pkg.version
 		data.author    = pkg.author.name
-		data.copyright = '\n\t<!-- '+pkg.name+' '+pkg.version+' (c) '+grunt.template.today('yyyy')+' '+pkg.author.name+' -->'
+		data.copyright = "\n\t<!-- #{pkg.name} #{pkg.version} (c) #{grunt.template.today('yyyy')} #{pkg.author.name} -->"

 		data.ga      = if data.ga      then data.ga      else '{TODO}'
 		data.addthis = if data.addthis then data.addthis else '{TODO}'
 		data.domain  = if data.domain  then data.domain  else '{TODO}'

 		data.css_common = ''
+
+
+		console.log data
+		return false

 		# copy base
@@ -46,7 +50,7 @@
 		else
 			src = path.skeleton.base

-		util.copy src+'/', out+'/', ['**', '**/.gitignore']
+		util.copy "#{src}/", "#{out}/", ['**', '**/.gitignore']

@@ -54,19 +58,19 @@

 		# theme
 		if data.theme
-			grunt.file.delete out+'/sources/css/libs/reset.css',
-			grunt.file.delete out+'/sources/css/libs/normalize.css',
-			grunt.file.delete out+'/sources/css/libs/html5boilerplate.css',
-			grunt.file.delete out+'/sources/css/libs/nwayo-boilerplate.less'
+			grunt.file.delete "#{out}/sources/css/libs/reset.css",
+			grunt.file.delete "#{out}/sources/css/libs/normalize.css",
+			grunt.file.delete "#{out}/sources/css/libs/html5boilerplate.css",
+			grunt.file.delete "#{out}/sources/css/libs/nwayo-boilerplate.less"
 		else
 			less.push 'nwayo-boilerplate'

 		# foundation
 		if data.layout is 'foundation'
-			grunt.file.delete out+'/sources/css/libs/reset.css',
-			grunt.file.delete out+'/sources/css/libs/html5boilerplate.css',
-			grunt.file.delete out+'/sources/css/libs/nwayo-boilerplate.less'
+			grunt.file.delete "#{out}/sources/css/libs/reset.css",
+			grunt.file.delete "#{out}/sources/css/libs/html5boilerplate.css",
+			grunt.file.delete "#{out}/sources/css/libs/nwayo-boilerplate.less"

 		# drupal
@@ -75,40 +79,40 @@

 			if data.layout is 'foundation'
 				less.push 'cms-drupal-zurbfoundation'
-				fs.renameSync out+'/STARTER.info', out+'/'+data.name+'.info'
+				fs.renameSync "#{out}/STARTER.info", "${out}/#{data.name}.info"

 			else
-				grunt.file.delete out+'/sources/css/libs/cms-drupal-zurbfoundation.less'
+				grunt.file.delete "#{out}/sources/css/libs/cms-drupal-zurbfoundation.less"

 		else
-			grunt.file.delete out+'/sources/css/libs/cms-drupal.less'
-			grunt.file.delete out+'/sources/css/libs/cms-drupal-zurbfoundation.less'
+			grunt.file.delete "#{out}/sources/css/libs/cms-drupal.less"
+			grunt.file.delete "#{out}/sources/css/libs/cms-drupal-zurbfoundation.less"

 		# magento
 		if data.cms is 'magento'
 			less.push 'cms-magento'
 		else
-			grunt.file.delete out+'/sources/css/libs/cms-magento.less'
+			grunt.file.delete "#{out}/sources/css/libs/cms-magento.less"

 		# sitecore
 		if data.cms is 'sitecore'
 			less.push 'cms-sitecore'
 		else
-			grunt.file.delete out+'/sources/css/libs/cms-sitecore.less'
+			grunt.file.delete "#{out}/sources/css/libs/cms-sitecore.less"

 		# no cms
 		if data.cms is ''
-			grunt.file.delete out+'/sources/css/misc/editor.less'
+			grunt.file.delete "#{out}/sources/css/misc/editor.less"

 		# build less loader
-		data.less += "@import 'libs/"+file+"';\n" for file in less
+		data.less += "@import 'libs/#{file}';\n" for file in less

 		# process data var
 		value = preprocess.preprocess(value,data) if (grunt.util.kindOf(value) is 'string') for value in data


[Gutter] Add back `includeDeprecatedAPIs` gate on `gutter` type decorations

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -1308,7 +1308,7 @@
   #
   # Returns a {Decoration} object
   decorateMarker: (marker, decorationParams) ->
-    if decorationParams.type is 'gutter' and not decorationParams.gutterName
+    if includeDeprecatedAPIs and decorationParams.type is 'gutter' and not decorationParams.gutterName
       deprecate("Decorations of `type: 'gutter'` have been renamed to `type: 'line-number'`.")
       decorationParams.type = 'line-number'
     @displayBuffer.decorateMarker(marker, decorationParams)


Add encoding getter and setter

Add encoding getter and setter

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -568,6 +568,16 @@
   # Essential: Returns the {String} path of this editor's text buffer.
   getPath: -> @buffer.getPath()

+  # Extended: Returns the {String} character set encoding of this editor's text
+  # buffer.
+  getEncoding: -> @buffer.getEncoding()
+
+  # Extended: Set the character set encoding to use in this editor's text
+  # buffer.
+  #
+  # * `encoding` The {String} character set encoding name such as 'utf8'
+  setEncoding: (encoding) -> @buffer.setEncoding(encoding)
+
   # Essential: Returns {Boolean} `true` if this editor has been modified.
   isModified: -> @buffer.isModified()


:racehorse: One `mergeIntersectingSelections` per text mutation

* Return fn value from `mergeIntersectingSelections` if supplied

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -836,7 +836,8 @@
   #      argument will be a {Selection} and the second argument will be the
   #      {Number} index of that selection.
   mutateSelectedText: (fn) ->
-    @transact => fn(selection, index) for selection, index in @getSelections()
+    @mergeIntersectingSelections =>
+      @transact => fn(selection, index) for selection, index in @getSelections()

   # Move lines intersection the most recent selection up by one row in screen
   # coordinates.
@@ -2248,6 +2249,7 @@

     [head, tail...] = @getSelectionsOrderedByBufferPosition()
     _.reduce(tail, reducer, [head])
+    return result if fn?

   # Add a {Selection} based on the given {Marker}.
   #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -24,7 +24,7 @@
     @methodOf TextScreen#
     @param {PixieCanvas} canvas The canvas to draw on
     @param {String} text The text to draw
-    @param {Object} options
+    @param {Object} options These include font, fon
     ###
     centerText: (canvas, text, options={}) ->
       font = options.font || I.font


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -11,6 +11,7 @@
       canvas.centerText
         y: App.height / 2 - 20
         text: titleText
+        color:

       canvas.font '14px Helvetica'


objectFilterChain

objectFilterChain

--- src/camera.coffee
+++ src/camera.coffee
@@ -15,10 +15,25 @@
     cameraRotation: 0
     transform: Matrix()
     scroll: Point(0, 0)
-    zSort: true

   currentType = "centered"
   currentObject = null
+
+  objectFilters = []
+
+  filterObjects = (objects) ->
+    for filter in objectFilters
+      objects = filter(objects)
+
+    return objects
+
+  transformFilters = []
+
+  filterTransform = (transform) ->
+    for filter in transformFilters
+      transform = filter(transform)
+
+    return transform

   transformCamera = (object) ->
     objectCenter = object.center()
@@ -71,6 +86,12 @@
       # TODO: Easing
       I.scroll = object.center()

+    objectFilterChain: (fn) ->
+      objectFilters.push fn
+
+    transformFilterChain: (fn) ->
+      transformFilters.push fn
+
   self.attrAccessor "transform"

   self.include(Bindable)
@@ -86,11 +107,7 @@
       canvas.context().rect(0, 0, I.screen.width, I.screen.height)
       canvas.context().clip()

-      # TODO Turn this zSort into a per camera object stream filter
-      # This will also enable filters like clipping region tests
-      if I.zSort
-        objects.sort (a, b) ->
-          a.I.zIndex - b.I.zIndex
+      objects = filterObjects(objects)

       canvas.withTransform self.transform(), (canvas) ->
         self.trigger "beforeDraw", canvas
@@ -98,6 +115,7 @@

       self.trigger 'flash', canvas

+  self.include(Camera.ZSort)
   self.include(Camera.Rotate)
   self.include(Camera.Zoom)
   self.include(Camera.Shake)


Searching for project's `.rubocop.yml` on project root folder

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -81,12 +81,21 @@
     else
       @autoCorrectBuffer(editor.getBuffer())

+  projectRootRubocopConfig: (filePath) ->
+    [projectPath, relativePath] = atom.project.relativizePath(filePath)
+    rubocopConfigPath = projectPath+'/.rubocop.yml'
+    if (fs.existsSync(rubocopConfigPath))
+      ['--config', rubocopConfigPath]
+    else
+      []
+
   autoCorrectBuffer: (buffer)  ->
     tempFilePath = @makeTempFile("rubocop.rb")
     fs.writeFileSync(tempFilePath, buffer.getText())
     commandWithArgs = atom.config.get('rubocop-auto-correct.rubocopCommandPath')
                                 .split(/\s+/).filter((i) -> i)
                                 .concat(['-a', tempFilePath])
+                                .concat(@projectRootRubocopConfig(buffer.getPath()))
     command = commandWithArgs[0]
     args = commandWithArgs[1..]
     options = { encoding: 'utf-8', timeout: 5000 }
@@ -123,6 +132,7 @@
     commandWithArgs = atom.config.get('rubocop-auto-correct.rubocopCommandPath')
                                 .split(/\s+/).filter((i) -> i)
                                 .concat(['-a', filePath])
+                                .concat(@projectRootRubocopConfig(filePath))
     command = commandWithArgs[0]
     args = commandWithArgs[1..]
     debug = atom.config.get('rubocop-auto-correct.debugMode')


fixed typo

fixed typo

--- app/models/appointment.coffee
+++ app/models/appointment.coffee
@@ -17,7 +17,7 @@
     description: "The agreed unit price value of this appointment for your reference"
     modify: ["new", "edit"]
     display: ["show"]
-  priceUnits: DS.attr "number",
+  priceUnits: DS.attr "string",
     label: "Units of Price"
     description: "The agreed units of price, for example: \"cents / pound\", \"dollars / metric ton\""
     modify: ["new", "edit"]


Add channel info

Add channel info

--- src/client.coffee
+++ src/client.coffee
@@ -6,6 +6,7 @@
 pingInterval = 60000

 User = require './user.coffee'
+Message = require './message.coffee'

 class Client extends EventEmitter
     constructor: (@host, @group, @email, @password, @options={wssPort: 443}) ->
@@ -16,7 +17,7 @@
         @self = null
         @channels = {}
         @users = {}
-
+        @teams = {}

         @ws = null
         @_messageID = 0
@@ -45,13 +46,22 @@
                 @emit 'loggedIn', @self
                 # Load userlist
                 @_apiCall 'GET', '/users/profiles', null, @_onProfiles
-
+                @_apiCall 'GET', '/channels/', null, @_onChannels
+                @_apiCall 'GET', '/teams/me', null, @_onTeams
                 @connect()
         else
             @emit 'error', data
             @authenticated = false
             @reconnect()
-
+
+    _onTeams: (data, headers) =>
+        if data
+            @teams = data
+            @logger.debug 'Found '+Object.keys(@teams).length+' teams.'
+        else
+            @logger.error 'Failed to load teams from server.'
+            @emit 'error', { msg: 'failed to load teams.' }
+
     _onProfiles: (data, headers) =>
         if data
             @users = data
@@ -59,6 +69,15 @@
         else
             @logger.error 'Failed to load profiles from server.'
             @emit 'error', { msg: 'failed to load profiles'}
+
+    _onChannels: (data, headers) =>
+        if data
+            @channels = data.members
+            @logger.debug 'Found '+Object.keys(@channels).length+' channels.'
+            @channel_details = data.channels
+        else
+            @logger.error 'Failed to get subscribed channels list from server.'
+            @emit 'error', { msg: 'failed to get channel list'}

     connect: ->
         @logger.info 'Connecting...'
@@ -136,9 +155,25 @@
                 @logger.debug 'ACK ping'
                 @_lastPong = Date.now()
                 @emit 'ping'
+            when 'posted'
+                m = new Message @, message
+                @emit 'message', m
+            when 'typing'
+                @emit 'typing', @getUserByID message.user_id, message
             else
-                @logger.info 'Received message type: '+message.action
-                @logger.info message
+                @logger.debug 'Received unhandled message type: '+message.action
+                @logger.debug message
+
+    getUserByID: (id) ->
+        @users[id]
+
+    getUserByEmail: (email) ->
+        for u of @users
+            if @users[u].email == email
+                return @users[u]
+
+    getChannelByID: (id) ->
+        @channels[id]

     # Private functions
     #


client: actually fix the silly parsing logic.

--- src/client.coffee
+++ src/client.coffee
@@ -154,9 +154,12 @@
                             throw "No `lib.#{ sig.use }`." if not lib_func?
                             args = _.map sig.args, (a) ->
                                 switch
-                                    when a is '@' geom_value
-                                    when _.isString(a) and a.startsWith '$' a.slice 1
-                                    else a
+                                    when a is '@'
+                                        geom_value
+                                    when _.isString(a) and a.startsWith '$'
+                                        a.slice 1
+                                    else
+                                        a
                             geom[key] = lib_func params, choices, args...
                     line = "v #{ geom.x } #{ geom.y } #{ geom.z }"
                 else


remove place from chart on change

remove place from chart on change

--- app/assets/javascripts/app/views/flight_profiles.js.coffee
+++ app/assets/javascripts/app/views/flight_profiles.js.coffee
@@ -54,6 +54,7 @@
     place_name = $(e.currentTarget).text()

     if (place_id)
+      @remove_place_from_chart()
       @add_place_to_chart(place_name, place_id)
     else
       @remove_place_from_chart()


fixed bug related to new dynamic tab not showing up active

--- app/scripts/directives.coffee
+++ app/scripts/directives.coffee
@@ -42,6 +42,7 @@
             ]

         s.setSelected = (index, ignoreCheck) ->
+            console.log("tab setSelected", index)
             if not ignoreCheck and index not of s.fixedTabs
                 index = s.maxTab

@@ -59,7 +60,11 @@
             watchHash()), 0

         s.newDynamicTab = () ->
-            $timeout (() -> s.setSelected(s.maxTab + 1, true)), 0
+            console.log("newDynamicTab s.maxTab", s.maxTab)
+            $timeout (() ->
+                s.setSelected(s.maxTab + 1, true)
+                s.maxTab += 1
+            ), 0


fix for previous commit

fix for previous commit

git-svn-id: 00f01ee4f729ccc52c3e336777c3e81b5adc88db@249868 ef392f78-ee14-0410-84e5-9db46baae5bb

--- app/scripts/directives.coffee
+++ app/scripts/directives.coffee
@@ -573,7 +573,6 @@
             </script>

<input
-                    id="korpAutocompleteInput"
                     autofocus
                     type="text"
                     ng-model="textInField"


get rid of typo creation of extra map

get rid of typo creation of extra map

--- modules/util/main/index.coffee
+++ modules/util/main/index.coffee
@@ -230,7 +230,6 @@
   else if obj instanceof hx.List
     new hx.List(obj.entries().map(hx.clone))
   else if obj instanceof hx.Map
-    new hx.Map obj.entries()
     new hx.Map(obj.entries().map(([k, v]) -> [hx.clone(k), hx.clone(v)]))
   else if obj instanceof hx.Set
     new hx.Set(obj.keys().map(hx.clone))


filter out empty solutions when clicking Fill Defaults

--- app/views/editor/component/ThangComponentConfigView.coffee
+++ app/views/editor/component/ThangComponentConfigView.coffee
@@ -104,13 +104,11 @@

   onClickFillDefaults: (e) =>
     e.preventDefault()
-    sources = {}
-    if source = @parent.data.source
-      sources.javascript = source
-
+
     sources = { javascript: @parent.data.source }
     _.extend sources, @parent.data.languages or {}
     solutions = _.clone(@data)
+    solutions = _.filter(solutions, (solution) -> not _.isEmpty(solution) )
     for language in _.keys(sources)
       source = sources[language]
       solution = _.findWhere(solutions, {language: language})
@@ -120,5 +118,5 @@
         language: language
         passes: true
       })
-
+
     @set('/', solutions)


tmp -> result

tmp -> result

--- jquery.transparency.coffee
+++ jquery.transparency.coffee
@@ -25,22 +25,22 @@
     values  = select(object, (key, value) -> typeof value == "string")
     objects = select(object, (key, value) -> typeof value == "object"  && not $.isArray(value))
     lists   = select(object, (key, value) -> $.isArray(value) )
-    tmp     = template.clone()
+    result  = template.clone()

     for key, value of values
       [klass, attribute] = key.split('@')
-      assign tmp, attribute, value if tmp.hasClass klass
-      tmp.find(".#{klass}").each ->
+      assign result, attribute, value if result.hasClass klass
+      result.find(".#{klass}").each ->
         assign $(this), attribute, value

     for key, value of lists
-      tmp.find(".#{key}").children().first().render(value)
+      result.find(".#{key}").children().first().render(value)

     for key, value of objects
-      tmp.find(".#{key}").render(value)
+      result.find(".#{key}").render(value)

     # Add rendered template to dom
-    context.before(tmp)
+    context.before(result)

   return context.remove() # Remove the original template from dom


fixed applicant dataTable() columns

fixed applicant dataTable() columns

--- app/assets/javascripts/applicants.js.coffee
+++ app/assets/javascripts/applicants.js.coffee
@@ -6,4 +6,5 @@
   $('#applicants').dataTable
     "columns": [null,
                   null,
-                  {"orderable" : false}],
+                    null,
+                      {"orderable" : false}],


Don't allow empty edits

Don't allow empty edits

--- app/assets/javascripts/goals_tree.js.coffee
+++ app/assets/javascripts/goals_tree.js.coffee
@@ -13,7 +13,12 @@
     @treeview.on('ready.jstree', => @treeview.jstree("open_all"))

   rename: (event) =>
-    name = prompt("New name?")
+    name = prompt("New name?", event.reference.text())
+    name = $.trim(name)
+
+    if _.isEmpty(name)
+      return
+
     data = $(event.reference).parent('li').data()
     @treeview.jstree('rename_node', event.reference, "(saving)")
     $.ajax(


Perform masonry tiling after fonts have loaded

Otherwise, the font load causes the text height/size to change, causing the tiles
to overlap.

--- app/assets/javascripts/portfolios.js.coffee
+++ app/assets/javascripts/portfolios.js.coffee
@@ -21,8 +21,9 @@
     )

   initializeMasonryLayout: ->
-    $('#other-projects').masonry
-      gutterWidth: 22
-      itemSelector: '.project'
+    $(window).load ->
+      $('#other-projects').masonry
+        gutterWidth: 22
+        itemSelector: '.project'

 new LoadingPage().checkForMissingPortfolio()


Actually run stylesheets-manager initializer

--- assets/scripts/app/initializers/stylesheets-manager.coffee
+++ assets/scripts/app/initializers/stylesheets-manager.coffee
@@ -13,3 +13,6 @@
 StylesheetsManagerInitializer =
   name: 'inject-stylesheets-manager'
   initialize: initialize
+
+Ember.onLoad 'Ember.Application', (Application) ->
+  Application.initializer StylesheetsManagerInitializer


closes #61 if we don't think it looks too funny...

--- app/pages/home.cjsx
+++ app/pages/home.cjsx
@@ -29,10 +29,11 @@

           <Translate component="div" className="tagline" content="homePage.hero.tagline" />
-          <Translate component="div" className="overview" content="homePage.hero.overview" />

           <Link to="classify" className="home-call-to-action-button"><Translate content="homePage.getStarted" /></Link>
           <Link to="about" className="home-call-to-action-button ghost-button"><Translate content="homePage.learnMore" /></Link>
+
+          <Translate component="div" className="overview" content="homePage.hero.overview" />

       <ProjectMetadata />


config: Add some simple rate estimation

config: Add some simple rate estimation

References #43

--- src/config.coffee
+++ src/config.coffee
@@ -156,6 +156,27 @@

   return errors

+estimateRates = (cfg) ->
+  rates = {}
+  for role, c of cfg
+    continue if role == '*'
+    rate = c.concurrency * c.maximum * (1 / c.processing)
+    rates[role] = rate
+
+  return rates
+
+main = () ->
+  p = process.argv[2]
+  f = fs.readFileSync p, 'utf-8'
+  parsed = parseConfig f
+  rates = estimateRates parsed
+  for role, rate of rates
+    perMinute = (rate*60).toFixed(2)
+    padded = ("                                     " + role).slice(-40);
+    console.log "#{padded}: #{perMinute} jobs/minute"
+
+main() if not module.parent
+
 exports.validate = validateConfig
 exports.parse = parseConfig
 exports.parseOnly = parse