diff --git a/assets/scripts/lib/travis/chunk_buffer.coffee b/assets/scripts/lib/travis/chunk_buffer.coffee new file mode 100644 index 00000000..b5cdf326 --- /dev/null +++ b/assets/scripts/lib/travis/chunk_buffer.coffee @@ -0,0 +1,92 @@ +get = Ember.get + +Travis.ChunkBuffer = Em.ArrayProxy.extend + timeout: 5000 + checkTimeoutFrequency: 1000 + start: 1 + next: 1 + + init: -> + @_super.apply this, arguments + + @lastInsert = 0 + + @set('next', @get('start')) + + @checkTimeout() + + if @get('content.length') + @get('queue.content').pushObjects @get('content').toArray() + + arrangedContent: (-> + [] + ).property('content') + + addObject: (obj) -> + @get('content').pushObject(obj) + + removeObject: (obj) -> + @get('content').removeObject(obj) + + replaceContent: (idx, amt, objects) -> + @get('content').replace(idx, amt, objects) + + queue: (-> + Em.ArrayProxy.create(Em.SortableMixin, + content: [] + sortProperties: ['number'] + sortAscending: true + ) + ).property() + + contentArrayDidChange: (array, index, removedCount, addedCount) -> + @_super.apply this, arguments + + if addedCount + queue = @get('queue.content') + addedObjects = array.slice(index, index + addedCount) + console.log 'Added log parts with numbers:', addedObjects.map( (element) -> get(element, 'number') )+'', 'current', @get('next') + queue.pushObjects addedObjects + @check() + @inserted() + + check: -> + queue = @get('queue') + next = @get('next') + + arrangedContent = @get('arrangedContent') + toPush = [] + + while queue.get('firstObject.number') <= next + element = queue.shiftObject() + if get(element, 'number') == next + toPush.pushObject get(element, 'content') + next += 1 + + if toPush.length + arrangedContent.pushObjects toPush + + @set('next', next) + + inserted: -> + now = @now() + @lastInsert = now + + checkTimeout: -> + now = @now() + if now - @lastInsert > @get('timeout') + @giveUpOnMissingParts() + @set 'runLaterId', Ember.run.later(this, @checkTimeout, @get('checkTimeoutFrequency')) + + willDestroy: -> + Ember.run.cancel @get('runLaterId') + @_super.apply this, arguments + + now: -> + (new Date()).getTime() + + giveUpOnMissingParts: -> + if number = @get('queue.firstObject.number') + console.log 'Giving up on missing parts in the buffer, switching to:', number + @set('next', number) + @check() diff --git a/assets/scripts/spec/spec_helper.coffee b/assets/scripts/spec/spec_helper.coffee index 338ffce5..5e469126 100644 --- a/assets/scripts/spec/spec_helper.coffee +++ b/assets/scripts/spec/spec_helper.coffee @@ -6,7 +6,6 @@ minispade.require 'app' waits(50) runs -> Travis.reset() - url = "/#{url}" unless url.match /^\// Travis.__container__.lookup('router:main').handleURL(url) diff --git a/assets/scripts/spec/unit/chunk_buffer_spec.coffee b/assets/scripts/spec/unit/chunk_buffer_spec.coffee new file mode 100644 index 00000000..399dcbb3 --- /dev/null +++ b/assets/scripts/spec/unit/chunk_buffer_spec.coffee @@ -0,0 +1,93 @@ +createChunk = (number, content) -> + Em.Object.create(number: number, content: content) + +describe 'Travis.ChunkBuffer', -> + it 'waits for parts to be in order before revealing them', -> + buffer = Travis.ChunkBuffer.create(content: []) + + buffer.pushObject createChunk(3, "baz") + buffer.pushObject createChunk(2, "bar") + + expect(buffer.get('length')).toEqual(0) + + buffer.pushObject createChunk(1, "foo") + + expect(buffer.get('length')).toEqual(3) + + expect(buffer.toArray()).toEqual(['foo', 'bar', 'baz']) + + it 'ignores a part if it fails to be delivered within timeout', -> + buffer = Travis.ChunkBuffer.create(content: [], timeout: 20, checkTimeoutFrequency: 5) + + buffer.pushObject createChunk(3, "baz") + + expect(buffer.get('length')).toEqual(0) + + buffer.pushObject createChunk(1, "foo") + + expect(buffer.get('length')).toEqual(1) + + waits 40 + runs -> + expect(buffer.get('length')).toEqual(2) + expect(buffer.toArray()).toEqual(['foo', 'baz']) + + buffer.destroy() + + it 'works correctly when parts are passed as content', -> + content = [createChunk(2, 'bar')] + + buffer = Travis.ChunkBuffer.create(content: content) + + expect(buffer.get('length')).toEqual(0) + + buffer.pushObject createChunk(1, "foo") + + expect(buffer.get('length')).toEqual(2) + expect(buffer.toArray()).toEqual(['foo', 'bar']) + + it 'works correctly when parts duplicated', -> + buffer = Travis.ChunkBuffer.create(content: []) + + buffer.pushObject createChunk(1, "foo") + buffer.pushObject createChunk(2, "bar") + buffer.pushObject createChunk(3, "baz") + + buffer.pushObject createChunk(2, "bar") + buffer.pushObject createChunk(3, "baz") + buffer.pushObject createChunk(4, "qux") + + expect(buffer.get('length')).toEqual(4) + expect(buffer.toArray()).toEqual(['foo', 'bar', 'baz', 'qux']) + + it 'fires array observers properly', -> + changes = [] + buffer = Travis.ChunkBuffer.create(content: []) + + observer = Em.Object.extend( + init: -> + @_super.apply this, arguments + + @get('content').addArrayObserver this, + willChange: 'arrayWillChange', + didChange: 'arrayDidChange' + + arrayWillChange: (->) + arrayDidChange: (array, index, removedCount, addedCount) -> + changes.pushObject([index, addedCount]) + ).create(content: buffer) + + buffer.pushObject createChunk(2, "baz") + + expect(buffer.get('length')).toEqual(0) + expect(changes.length).toEqual(0) + + buffer.pushObject createChunk(1, "foo") + + expect(buffer.get('length')).toEqual(2) + expect(changes.length).toEqual(1) + expect(changes[0]).toEqual([0, 2]) + + it 'sets next to start if start is given at init', -> + buffer = Travis.ChunkBuffer.create(content: [], start: 5) + expect(buffer.get('next')).toEqual(5)