96
Technical University of Denmark 02220 Distributed Systems Canva - magic mix drawings s131181 | Vlad Manea s131185 | Laura-Ariadna Stroe s131044 | Nanna Gudrun Hjaltalin May 19, 2014 1

Canva - magic mix drawings - Distributed Systems course 2014

Embed Size (px)

Citation preview

Page 1: Canva - magic mix drawings - Distributed Systems course 2014

Technical University of Denmark

02220 Distributed Systems

Canva - magic mix drawings

s131181 | Vlad Manea

s131185 | Laura-Ariadna Stroe

s131044 | Nanna Gudrun Hjaltalin

May 19, 2014

1

Page 2: Canva - magic mix drawings - Distributed Systems course 2014

Table of contents

pag. 6 ...... 1. Introduction

pag. 6 ...... 2. Design

pag. 7 .......... 2.1. Paradigms

pag. 9 .............. 2.1.1. Event driven drawing

pag. 9 .............. 2.1.2. Client - Server

pag. 10 .............. 2.1.3. HTTP available server

pag. 11 .............. 2.1.4. TCP event based socket reliable communication

pag. 12 .............. 2.1.5. Branch driven repository

pag. 13 .......... 2.2. Architecture

pag. 13 .............. 2.2.1. System architecture

pag. 15 .............. 2.2.2. Client side event based drawing system

pag. 16 .............. 2.2.3. Full stack branching system

pag. 16 .................. 2.2.3.1. Branch internal representation

pag. 17 .................. 2.2.3.2. Overview of branch procedures

pag. 18 .................. 2.2.3.3. Checking out a branch

pag. 19 .................. 2.2.3.4. Saving a branch

pag. 20 .................. 2.2.3.5. Merging branches

pag. 21 .................. 2.2.3.6. Blocking and unblocking

pag. 22 .............. 2.2.4. Full stack chat system

pag. 23 .............. 2.2.5. Test scenarios

pag. 23 .................. 2.2.5.1. Chat test scenario

pag. 24 .................. 2.2.5.2. Drawing test scenario

pag. 26 .................. 2.2.5.3. Branching test scenario

pag. 29 .................. 2.2.5.4. Blocking test scenario

pag. 31 ...... 3. Implementation

2

Page 3: Canva - magic mix drawings - Distributed Systems course 2014

pag. 31 .......... 3.1. Technologies

pag. 31 .............. 3.1.1. Node.js web framework

pag. 32 .............. 3.1.2. Fabric.js canvas drawing framework

pag. 32 .............. 3.1.3. Socket.IO web communication framework

pag. 32 .............. 3.1.4. Jasmine.js test framework

pag. 32 .......... 3.2. Workflow

pag. 32 .............. 3.2.1. Timeline

pag. 33 .............. 3.2.2. Tasks

pag. 34 .............. 3.2.3. Repository

pag. 34 .............. 3.2.4. Build

pag. 34 .............. 3.2.5. Deployment

pag. 35 .............. 3.2.6. Test

pag. 35 .............. 3.2.7. Team

pag. 35 ...... 4. Conclusions

pag. 36 ...... List of references

pag. 39 ...... Appendix A

pag. 39 .......... A.1. Piping command line API modules

pag. 39 .............. A.1.1. Strength

pag. 40 .............. A.1.2. Weakness

pag. 40 .............. A.1.3. Opportunity

pag. 40 .............. A.1.4. Threat

pag. 40 .......... A.2. Google Docs LaTeX addon

pag. 40 .............. A.2.1. Strength

pag. 40 .............. A.2.2. Weakness

pag. 40 .............. A.2.3. Opportunity

pag. 40 .............. A.2.4. Threat

pag. 40 .......... A.3. Biker real time tracking

3

Page 4: Canva - magic mix drawings - Distributed Systems course 2014

pag. 41 .............. A.3.1. Strength

pag. 41 .............. A.3.2. Weakness

pag. 41 .............. A.3.3. Opportunity

pag. 41 .............. A.3.4. Threat

pag. 41 ...... Appendix B

pag. 41 .......... B1. Server side code

package.json

app.js

chat.js

config.js

http.js

io.js

repository.js

routes.js

server.js

pag. 54 .......... B.2. Client side code

circle.js

ellipse.js

line.js

polyline.js

rectangle.js

text.js

triangle.js

chat.js

index.js

modifier.js

painter.js

4

Page 5: Canva - magic mix drawings - Distributed Systems course 2014

repository.js

socket.js

ui.js

style.css

index.jade

pag. 82 .......... B.3. Test code

server-repository-spec.js

server-chat-spec.js

pag. 91 ...... Appendix C

pag. 91 .......... C.1. A passing build

pag. 94 .......... C.2. A failing build

pag. 95 .......... C.3. The latest build and deploy runs

5

Page 6: Canva - magic mix drawings - Distributed Systems course 2014

1. Introduction The stated purpose of this project is to implement a self-chosen distributed system.

Our aim was to find a real problem, so that its solution was complex enough to fit

the project requirements for the course, while simple enough to permit

implementation in the given time frame. We aimed for a project whose lifespan may

have been prolonged beyond the course, and looked for a challenging and rewarding

problem. We wished to learn many things while solving it, and enjoy our work.

We made use of existing project requirements, proposed in [1] as a baseline. After

brainstorming, we ended up with four ideas which met the objectives above: a

collaborative drawing application, a modular framework for piping commands, a

Google Docs LaTeX addon, and a real time biker tracker. We received approval for all

of these, and then decided by vote on the drawing application. The other projects

can be found in Appendix A.

Collaborative drawing is a problem already solved by Google with Drawings [2] and

Microsoft with Onenote [3]. Google also allows users to switch between different

versions of these diagrams in time. This model works great for playful draw and

simple diagrams made by small teams. But in a business environment, many times

parts of larger teams propose different drawings of diagrams, or work on parts of the

final drawing separately. The current solutions make “choosing the best and losing

the rest” a hard burden on the teams.

The strength of this project, aside from meeting our objectives above, consists of us

having worked with web technologies before. We already have the basics, and can

use these to learn more. Our work has the opportunity to jumpstart a useful and

open-source modular product for enterprise and students, while still being suited

for playful drawing. Two of us actually faced the problem at a different course.

Our weakness is that not all team members have prior knowledge of versioning and

branching, and none has experience in developing a system with these features. We

were threaten by the project taking more than planned, which might have required

us to prioritize the features and discard some.

With these in mind, we decided to tackle the problem. We wish to empower users

to not only work together, but also review and mix their work. To achieve this goal,

we have developed canva [4]: a universally available collaborative drawing

application, where groups of users can create multiple parts of the same drawing

independently, and then discuss and combine them into magic.

2. Design

Our system has been designed to implement the following features:

6

Page 7: Canva - magic mix drawings - Distributed Systems course 2014

1. A canvas on the client, which allows users to create custom shapes; each

shape is defined by a module of its own

2. A branch based repository system, which allows users to share their drawings

with others, start from scratch or improve existing versions, and combine

their results by choosing only the relevant shapes

3. A system that allows users to work in real time on the same canvas, such

that only one saves changes made by anybody else in that group

4. A messaging system, which allows user conversation during canvas drawing

collaboration

Each feature is described in its corresponding subsection.

2.1. Paradigms

Our distributed system implements features based on a clear set of paradigms.

These can be seen at work in Fig. 1.

7

Page 8: Canva - magic mix drawings - Distributed Systems course 2014

Fig. 1. The paradigms in our distributed system at work. There are two

clients who connect to the server to work on the same drawing. Both

connect to fetch the page, and then to establish the socket handshake.

Both clients concurrently check out a branch. Client 2 performs a

change by adding a circle, commits the change locally, and then saves

the branch onto the server. When client 1 checks out the branch again,

it will obtain the latest version, and will be in sync with client 2.

8

Page 9: Canva - magic mix drawings - Distributed Systems course 2014

2.1.1. Event driven drawing

The HTML5 canvas tag [5] does not provide implementation for each of the

geometric shapes. This can be implemented, yet the canvas does not provide an out

of box event system for shape management, which would allow moving, rotating and

other operations.

We drew our attention upon frameworks that could help us. We were in search of a

drawing system which handled shape events on the canvas, and also had primitive

shapes implemented. Our task would now be to implement the drawing of these

primitive shapes by using the events we were provided.

For instance, when drawing a circle, one first clicks on the canvas surface, and then

moves the mouse onto a different position. In all this time, the user can see a

preview circle on the canvas, such that the first click location is its center, and the

current location defines the radius. When the user releases, the operation ends, and

the circle is added to the canvas. This flow is depicted in Fig. 2.

Fig. 2. Two events on the canvas define the center and the radius of a

circle. These events can be seen as transitions in a deterministic finite

state machine. We have created a DFSM for each shape type.

2.1.2. Client - Server

We need to implement a system that manages the clients and their drawings, such

that if a new change appears in one drawing, it will be made apparent to all involved

clients, but not more.

For example, if three teams of clients work on three separate drawings, each group

would have a set of clients working on the same drawing. We want the server to be

able to manage this group and other groups simultaneously and independently. This

situation is depicted in Fig. 3.

9

Page 10: Canva - magic mix drawings - Distributed Systems course 2014

Fig. 3. The server handles groups separately. This means that a client

will be able to obtain the changes made by other clients in the same

group, but not other clients and groups.

The current browser based collaborative tools such as [2] implement the client -

server paradigm. We did not see the advantages of implementing a more

complicated, decentralized paradigm, such as peer to peer [6].

2.1.3. HTTP available server

Our clients are using the web for collaboration, so it naturally makes sense to use

the web protocol and technology there is. This way, users will not have to install any

runtime environment, such as JRE [7], before using our application.

The HTTP protocol [8] is widely known and used by devices worldwide. From our

project perspective, its main advantages are that it does require nothing more than

a browser on the client, and that it can be used on both desktop and mobile devices

out of the box, as in Fig. 4.

In our project we use an initial HTTP request for fetching the initial page. This makes

it a RIA [9], which allows easy user navigation: the person only has to use the client

to browse there and start drawing.

10

Page 11: Canva - magic mix drawings - Distributed Systems course 2014

Fig. 4. Sequence diagram of a communication. HTTP is a understood by

many types of clients. This makes our application universally available.

2.1.4. TCP event based socket reliable communication

The information related to the drawing has to be sent and received accurately. In

order to achieve this, we need to ensure that information about the canvas is

reliably sent and received. It is crucial that each client is notified on any change, and

receives the correct change, if an active collaboration takes place. The reliability and

message order keeping properties of TCP allow us to keep the branches on the

client and server synchronized. On the other side, UDP is more suited for our

simple message transmission, and we considered implementing it there.

Message transmission reliability is only relevant in the context of an underlying

reliable system. In this case, one which is either single threaded or allows locks, so

that resources in the critical section are consistent at any time. The data structures

for storing branches and users have to be either accessed by a single thread, or

locked on write while reading. The implementation of this paradigm must therefore

have one of these two properties.

An example on how the sockets work is explained in Fig. 5. We assume that

someone adds a line on the canvas during collaboration, and we want every client to

be notified with the updated canvas, so they can continue drawing on top of it. A

missing or misordered package would prevent the client from receiving the updated

canvas, and would move it out of sync.

In order to have client notification, we went to sockets, which work in TCP only. We

are aware that chat messaging might also be done with UDP, for e.g., by using a

technology such as [10]. Since the messages are small information, we preferred to

use TCP and have a consistent codebase by using a TCP socket implementation.

11

Page 12: Canva - magic mix drawings - Distributed Systems course 2014

Websockets is a set of protocols [11] which provides a full duplex reliable

communication channels over a TCP connection [12], and is supported by multiple

browsers such as Google Chrome, Internet Explorer, Firefox, Safari and Opera. It did

not make sense to use pure HTTP for update notifications during drawing

collaboration, because it had to be done in real time, and HTTP would still require

the client to send requests to the server, by using AJAX [13] or similar. With

websockets, a client can listen for changes coming from the server without polling,

and it can pass messages in both directions at the same time (full duplex). [14]

mentions a list of disadvantages of pure HTTP as opposed to websockets.

Three out of the five signs described in [15] apply to our project, which indicates a

true need for websockets. First, data must flow both ways and simultaneously.

Second, the reference claims that websockets also provide a 3xlatency decrease

and a 1000x space decrease. Third, we need to avoid the server polling.

Fig. 5. Sequence diagram of reliably emitting a change in the drawing,

in this case the red line. One client emits the change to the server

through the open socket, and then the server emits the change to all

sockets. Now all clients have received the new line, without extra

requests to the server.

2.1.5. Branch driven repository

In order to support different versions, or parts, of the same drawing, one must use a

system which is able to combine changes. The source control repositories [16], [17]

[18] have solved this problem already, by allowing a single version path where

clients sync, commit, update, and resolve.

The Git system also resolved the problem of working on different branches, which

would allow independent work and simpler merge different branches at the end. A

flow example for Git is [19].

For example, we consider a scenario in which there are two teams with different

versions of a solution to a common problem. The teams start from an older diagram,

12

Page 13: Canva - magic mix drawings - Distributed Systems course 2014

and propose different improvements to it. Their proposals may have overlapping

parts. After they finish working on their individual branches, they might want both

solutions combined, meaning they want to keep the relevant parts from each of the

solutions, and discard the unnecessary data from both of them. Therefore, it is

necessary to have a branching system that gives the clients the possibility to also

merge, in our case, their drawings. Such a process is explained in Fig. 6.

Fig. 6. A branch driven repository. The circles represent new versions.

There is a master branch, from which all branches are created - here

depicted with the south-east arrows. From time to time, branch

integrations are performed - here depicted with the north-east arrows.

Some branch integrations might yield to conflicts which require

resolution, e.g., by merging.

2.2. Architecture

The client and server are implemented in a symmetrical way, such that module

cohesion increases. The client - server communication takes part by using socket

emit and socket-on events in the two sister socket modules, one on the server and

one on the client.

2.2.1. System architecture

The sister repository / chat modules on the server and on the client manage

branches / users at the server and client level, respectively. The client contains

additional modules for painting shapes, modifying shapes and interaction with the

UI.

The client also contains plug-in modules for various geometric shapes, which are

registered to the painter in the index file. We have applied a lightweight version of

the strategy pattern: when the user selects a shape name to be drawn, such as

square, then the square module is used to update the canvas.

The server additionally contains modules for HTTP communication and MVC route

management - there is a single route, which corresponds to the single rich page.

In Fig. 7 and Fig. 8 the client and server class diagrams can be seen.

13

Page 14: Canva - magic mix drawings - Distributed Systems course 2014

Fig. 7. The client class diagram.

Fig. 8. The server class diagram.

14

Page 15: Canva - magic mix drawings - Distributed Systems course 2014

In the prototype stage, all code was implemented in a single class . However, after 1

the prototype was functional, we separated the concerns in different modules. The

repository and chat implementations on both client and server were separated from

the socket managing class. The UI update functionality was also removed from the

socket managing class. This allowed the socket class to only know about the socket

instance, and the ui know about the jQuery objects. All responsibility regarding

branches was passed to the repository class, and all responsibility regarding

participants and messages was passed to the chat class.

The modular structure allowed us to implement and use the drawing and branching

features independently: we worked with figures in the canvas, and with plain

numbers as objects in the branches. This has proven beneficial in two situations.

The first was the integration of canvas objects into the branching system. Having all

implementation done in these two separate modules, we could very easily

integrate them. For instance, in order to commit the current state of the canvas, all

we had to do was to to pass the output of the canvas, i.e., the result of the function

canvas.toDatalessJSON()as the objectToCommitparameter of commitBranch,

and follow the same pattern in other branching functions. The entire integration took

around 15 minutes.

The second was the ability to test the server repository and chat functionality

independently, without requiring socket objects or mocks in the test harness.

2.2.2. Client side event based drawing system

We have implemented modules for the following geometrical shapes: circle, ellipse,

rectangle, triangle, line, polyline, and text box. The user can add one of these

shapes to the canvas by clicking, dragging and releasing. The farther the drag, the

larger the final object. A preview of the object is available when dragging.

Each module implements a deterministic finite state machine. For instance, the

circle module has three states. The first state corresponds to idle, and it is the start

state. The second state corresponds to a circle whose center is known, but whose

radius is unknown. The third state corresponds to a circle whose both center and

radius are known. When the user selects the circle button and then clicks on the

canvas, the DFSM goes from state 0 to state 1. At this state, the click location

corresponds to the center. When the user releases the click, the DFSM goes from

state 1 to state 2. The center and this location form a segment of length equal to the

radius, and the circle is drawn onto the canvas. Then the DFSM is triggered to

change state from 2 to 0, which is idle again. The DFSM manager automatically

moves it from any state to state 0 when a different DFSM is initiated. The creation of

a circle can also be seen in Fig. 2.

1Our programming language of choice does not use classes, yet it is possible to simulate a light set of properties and patterns that apply to classes, such as encapsulation and dependency injection.

15

Page 16: Canva - magic mix drawings - Distributed Systems course 2014

This modular structure proves beneficial, because all shapes are treated the same

by the painter, and more modules can be easily added if registered in the index file.

This will be helpful if the project lifespan is increased as open-source, and more

developers adhere by writing shapes modules.

The user can globally choose the border and fill color of all objects to be next

created, and the border thickness. We have implemented a lightweight version of

the flyweight design pattern for this: the color and thickness attributes are simply

inserted into all objects. By using the text field, the user can add text, for e.g., if

willing to describe something on the picture. We can see the drawing shapes

existing in our app in Fig. 9.

Fig. 9. The states of a canvas during drawing shape types, in order:

a circle, a triangle, a line, an ellipse, a polyline, a text box.

2.2.3. Full stack branching system

We want the our clients to be able to draw not only at the same time on the same

branch, but also separately, with the possibility of combining the drawings when

they want. Our branch system consists of a master branch, and a set of other

branches, created from the master branch. When one branch is created from

another, a version is created on the new branch, such that the object in that version

is a clone of the object in the head of the branch from which the creation is done.

After separating from a branch, our clients need to merge their work with any other

branch.

2.2.3.1. Branch internal representation

The branching system can be seen as a directed acyclic graph. The node with zero

inner degree corresponds to a primordial empty version of the master branch. This

node is created as part of the main branch when the branching system initializes. A

path from the primordial master node to any node corresponds to the master

branch, followed by the branch this version node belongs to. A version is a fixed

state of the branch at a certain point in time. In our concrete case, states are in the

universe of geometrical shapes placed on a canvas. This layout can be seen in Fig.

10.

16

Page 17: Canva - magic mix drawings - Distributed Systems course 2014

Fig. 10. Branching system internal layout, situated in both client and

server. A node in one branch may or may not point to a node in

another branch, but it always points to a version node. The exception

to this rule is the master primordial version node.

2.2.3.2. Overview of branch procedures

The checkoutBranch(branchName) procedure allows the retrieval of the latest

version of the branch with that name from the server. The master branch, along with

its primordial empty version, got created when the server initializes, and can be

checked out immediately: the user can either click the master button and then the

checkout button in order to checkout from the master branch. After the client is

checked out on the master branch, he can either start drawing in that branch, or

create another branch from it. The master branch can not be deleted, so that there

will always be a master branch.

To start working on a branch, the client has to checkout that branch first, which can

be done by pressing the button with the name of the branch and then pressing the

checkout button. If the user does not want to draw on the master canvas, he has to

create a new branch.

At any moment in time, createBranch(branchName)creates a new branch with

that name on the server, such that its primordial version points to the head of the

currently checked out branch. In order to create a branch the user has to click the

create button and then enter a name for the branch. At that point, a button with the

name of the new branch will appear next to the master branch button, so that from

this point on that branch can be checked out whenever necessary. This new branch

will have all the information from the master branch up to the point when it was

created. When a branch is created it is automatically added to the server. The user

cannot create a branch if the underlying branch is not updated, i.e., it has changes

not saved on the server. This situation is depicted in Fig. 11.

17

Page 18: Canva - magic mix drawings - Distributed Systems course 2014

Fig. 11. Creating a branch. The dotted lines depict paths not saved on

the server. In the left drawing, some changes were made to the

underlying branch and were not saved; therefore, the create is

aborted. In the right drawing, the branch is up to date with the server;

therefore the create is completed. Every primordial node of the new

branch points to the underlying node.

To save the changes locally withcommitBranch(objectToCommit), the client must

use the commit button. This procedure saves the changes in the current branch,

without sending them to the server. The client cannot commit on a branch if it is not

the checked out branch.

The saveBranch(branch)procedure adds a new branch if the branch did not exist,

or attempts to save it on the server. If both existing server and incoming client

branches have different head versions, the save is aborted and a conflict notification

is sent back to the client.

The mergeBranch(branchName) procedure merges locally the branch with the

specified name into the current branch.

The deleteBranch(branchName) inactivates the branch with the provided name.

To delete a branch one must be checked out on that branch, and select the delete

button. From that moment on, the branch becomes globally inactive. We decided to

make it inactive rather than completely deleting it, because there might be other

branches who point to one node in the branch to be deleted. Once a branch is

inactive, it acts as if it does not exist anymore, meaning that one can no longer

operate on that branch.

2.2.3.3. Checking out a branch

In this section changes are considered committed changes, so they appear as head

versions in the branches. On branch checking out on the client, there will be one of

the following situations.

If one has checked out on a different old branch before, and it made changes in that

old branch, then the changes must be committed in the old branch before checking

out a new branch. Otherwise, the changes are discarded. This situation can be seen

in picture 1 of Fig. 12.

18

Page 19: Canva - magic mix drawings - Distributed Systems course 2014

If the user wishes to check out a different branch, but there is no change in the old

branch, there is no problem. The user can simply check out the new branch. The

process can be seen in picture 2 of Fig. 12.

If the user wishes to check out the same branch, i.e., fetch the latest updates from

the server, there are four possible situations, as depicted in the pictures 3.1, 3.2,

3.3, 3.4 of Fig. 12.

1. None have changes. In this case, nothing happens.

2. The server branch has no changes, and the client has. In this case nothing

happens; the client can continue working on his branch.

3. The client branch has no changes, but the server has. In this case, the client

branch will be updated to the final version of the server branch.

4. Both have changes, and a conflict appears. To solve this, the user is asked to

merge the branches. The merge process is explained in the following pages.

Fig. 12. Checking out. A dashed line corresponds to a committed, but

not saved change. The red and magenta versions correspond to two

client branches. The green versions correspond to the server branch.

2.2.3.4. Saving a branch

Unlike the commit procedure, the save procedure is made on both client and

server. The user can save a branch by pressing the save button while being checked

out to that branch. There are two situations.

19

Page 20: Canva - magic mix drawings - Distributed Systems course 2014

If the branch was not saved before, then it is now created and added to the server.

If the branch was saved before, then it follows that it already exists on the server. In

this case, there are four possible scenarios.

If neither the client, nor the server made any changes, then they have the same

versions, and their head versions are the same. In this case, nothing happens, since

there is no need for any update or merge.

If the client made changes to the branch, while the server did not, then the client is

ahead of the server, and his changes are to be added to the server. After this

procedure, both client and server have the same head version.

If the client made no changes, but the server did, then the server is ahead of the

client and nothing happens. To get the updated version of the branch, the client will

have to check out the branch.

If both server and client made changes, there is a conflict. There are three important

values, which we denote conflicting values. The first is the last common version of

the client and the server, i.e., the latest version where they have the same

information. The second is the last version in the client branch. The third is the last

version in the server branch. The save aborts, and the server requires the client to

check out to get the last version. At check out, the client will enter the 3.4 case

depicted in Fig. 12. Here the client is required to merge the conflicting branches.

It is easy to observe these four cases are similar to the four same branch check out

cases. The similarity is further explained in the following section.

2.2.3.5. Merging branches

The merge feature is used in two situations. First, at check out, upon a failed save -

if the user decides to check out. Second, when the user merges the heads of two

two different branches locally.

In this scenario, a user is faced with the situation in which both client and server

made changes, and was requested to merge. At this point, the user will see four

canvases instead of one. Merging in this case means moving objects from one

canvas to another, or to the recycle bin. These actions can be done by clicking on

each object in particular. If an object from the left or the right canvas is clicked, then

that object will move to the middle canvas, which represents how the merged

version in the branch will look after the process. If one clicks on an object in the

middle canvas, and that object is part of the common part of the two versions, then

that object will be sent to the recycle bin. If the object in the middle, which was

received by one of the left or right versions, is being clicked, then it is sent back to

the canvas it belonged to. Things sent to the recycle bin can be restored by being

clicked. This interaction ensures complete undo capability.

A scenario in which merge is required can be seen in Fig. 13. The canvas to the left

represents the changes committed by the client in this branch, and the canvas to

20

Page 21: Canva - magic mix drawings - Distributed Systems course 2014

the right represents the changes saved on the server in this branch, by other clients.

The middle canvas initially depicts the last version in which both branches were

equal. Concretely, they both had the square, but the client the committed a circle,

while on the server there was a triangle added. In the image, the rectangle which is

in both versions, noted left and right, is faded out in gray and not selectable. This is

because clicking moves them to the middle canvas, but it already exists there. This

will only duplicate the object, putting them on top of each other. After all the

desired shapes are placed in the middle canvas, the user can click the ok button to

merge. The user can also cancel at any point, which also aborts the underlying check

out, if any.

Fig. 13. Merging branches. There are four canvases: your changes on

the left, their changes on the right, the baseline (and also final version

once the ok button is clicked) in the center, and the recycle bin below

2.2.3.6. Blocking and unblocking

We wish to allow the branching system to help users draw in two ways: first,

separately, so that any of their saved changes does not affect other users checked

out on that branch; and second, such that they can collaborate on drawing in real

time. In order to achieve these two separate objectives, we implemented a system

that blocks the save operation for all except one client, and allows for client

notification on any change other client is doing in that branch. This is where we

harness the power of sockets the most! At any time when there is no such block, a

client must check out the branch in order to receive the updated version of that

branch.

To let clients draw on the same canvas in real time, we created a blocked(data)

function. When a client blocks, it is given a unique token which validates its control

over saving that branch. A client can only block a branch if the branch is active,

which means it was not deleted, and if it is not already blocked by another client.

When a client blocked a branch, any other client can check out that branch and

make changes. The change is sent to the server, which notifies all participants that a

change has been made. Only the client having the token automatically commits and

21

Page 22: Canva - magic mix drawings - Distributed Systems course 2014

saves the change in the blocked branch. If another client who checked out on that

branch tries to save, they will receive an error message, because in order to save,

the client must have the token that was given at blocking. When the client in control

of that branch wants to release control, it will turn the token back in, and unblock

the branch, making it available for blocking by others, as depicted in Fig. 14.

If the block lasted for more than a specified period of time, a new block attempt will

succeed. This is useful for the case when the client who blocked loses connection

and cannot unblock anymore.

Fig. 14. A scenario for the block and unblock. There are two clients.

Assuming the second and third blocks are issued before the timeout,

the second client can only block the branch successfully after the first

client unblocked that branch.

2.2.4. Full stack chat system

In order to allow users to communicate without leaving the drawing application, we

have developed a simple chat system on top of websockets. When a user connects

to our application and also when sends a message to the server, all other clients are

notified a new participant or message appeared, and their clients update the user

interfaces with the new data. We have used the tutorial [20] for learning how to work

with websockets and get our chat up and running.

In our chat each participant can register with his or her name, and start chatting

right away. If the user does not register with a name, he will appear as anonymous.

The participant is automatically registered as anonymous when he enters the

website. Even if two participants have the same name, there will be no conflict when

22

Page 23: Canva - magic mix drawings - Distributed Systems course 2014

sending the information, because one object is identified not only by the name of

its author, but also by an the client socket id.

2.2.5. Test scenarios

We have split the testing of our application into automatic and manual.

We have tested automatically all responses the repository and the chat provide for

various operations. For each operation, we tested both valid and invalid input. This

provides us trust that the server performs correctly regardless of client message

structure . A sample test case can be seen in Fig. 15. 2

it("should trigger ok if token is valid", function() { var branchName = "existingBranch"; var participantId = 1; repo.saveBranch(

{branch: {name: branchName, nodes: [], active: true}}); var blockResponse = repo.block(

{participant: {id: participantId}, branch: {name: branchName}});

var result = repo.unblock({participant: {id: participantId},

branch: {name: branchName}, token: blockResponse.token});

expect(result.status).toEqual('OK'); expect(result.branch.name).toEqual(branchName); expect(repo.isBlocked(branchName)).not.toBeTruthy();

});

Fig. 15. Testing the invalid token.

On the client we could not make use of the server testing framework, due to

problems integrating the vanilla JavaScript files into the Node tests. We tried to no

avail to make use of the advice at [21].

We wanted to make sure our features, the chat, the drawing, the collaborative

drawing and the branching system would work before we move on to the next step.

Therefore we tested manually our application at every iteration.

In the last iteration of our project, we created and ran the following manual test

scenarios, covering the full stack.

2.2.5.1. Chat test scenario

Performed action Expected result

connect with a client there is no canvas in the user interface

2 We are aware of the fact that we did not perform security testing beyond the protocols we created for branching and blocking branches. This means that a missing token will be caught, while a spoofing attack on the token passing will not.

23

Page 24: Canva - magic mix drawings - Distributed Systems course 2014

connect with the first client its name appears as anonymous among the participants in the user interface; its name is followed by the “you” mark

write a message and click share the message appears in the user interface

connect with the second client there is no message visible; its name appears among the participants, but the other appears too; its name is followed by the “you” mark

write a message and click share the message appears on both clients.

change name of the first client, and focus out of the field without sending any message

both clients get the updated name

This scenario run did not uncover issues.

2.2.5.2. Drawing test scenario

Performed action Expected result

connect with a client there is no canvas in the user interface

click the master (branch) button

click the check out button a popup appears to notify that the branch has been fetched

click the ok button to approve the canvas appears in the user interface

click the circle button

click the canvas, keep the button clicked, and move the mouse

a preview circle is dynamically generated while moving the mouse

release the click button of the mouse, while being still in the canvas

a circle is created, instead of the last preview circle

click the ellipse button

click the canvas, keep the button clicked, and move the mouse

a preview ellipse is dynamically generated while moving the mouse

release the click button of the mouse, while being still in the canvas

an ellipse is created, instead of the last preview ellipse

click stroke color, select a color, click outside of canvas or picker to select the color

24

Page 25: Canva - magic mix drawings - Distributed Systems course 2014

click the rectangle button

click the canvas, keep the button clicked, and move the mouse

a preview rectangle is dynamically generated while moving the mouse

release the click button of the mouse, while being still in the canvas

a rectangle is created with the stroke color, instead of the last preview rectangle

move the stroke width slider

click the triangle button

click the canvas, keep the button clicked, and move the mouse

a preview triangle is dynamically generated while moving the mouse

release the click button of the mouse, while being still in the canvas

a triangle is created with the stroke color and width, instead of the last preview triangle

click the text button

click the canvas a popup asking for the text appears

introduce the text and click ok the text appeared, such that its top left corner is the point where clicked

This scenario run did not uncover issues.

2.2.5.3. Branching test scenario

Performed action Expected result

connect with the first client there is no canvas in the user interface

click the master (branch) button

click the check out button a popup appears to notify that the branch has been fetched

click the ok button to approve a canvas appears in the upper part of the user interface; assume the canvas has no content

draw a shape on the canvas the shape appears on the canvas

click the commit button a popup appears to notify that the change has been committed (locally, only on the client)

click the save button a popup appears to notify that the branch master has been saved

connect with a second client there is no canvas in the user interface

25

Page 26: Canva - magic mix drawings - Distributed Systems course 2014

click the master (branch) button

click the check out button a popup appears to notify that the branch has been fetched

click the ok button to approve a canvas appears in the upper part of the user interface; the canvas has the content drawn by the first client

draw a shape on the canvas the shape appears on the canvas

click the commit button a popup appears to notify that the change has been committed (locally, only on the client)

click the ok button to approve

click the save button a popup appears to notify that the branch master has been saved

click the ok button to approve

switch to the first client nothing changed in the canvas for this client; there is only the shape drawn previously by this client

draw a shape on the canvas the shape appears on the canvas

click the commit button a popup appears to notify that the change has been committed (locally, only on the client)

click the ok button to approve

click the save button a popup appears to notify that the save was not possible because there is another save on this branch; the user is asked to check out the branch to get the latest changes

click the ok button to approve

click the master (branch) button

click the check out button the merge screen appears; the shape drawn initially is grey; the shape by this client is in the left; the shape by the other client is in the right; the shape drawn initially, which was saved on the server, appears in the middle canvas as well; the recycle bin canvas is empty

click the shape from your changes (the left canvas)

the shape moves from left to middle canvas

click the shape from their changes (the right canvas)

the shape moves from right to middle canvas

26

Page 27: Canva - magic mix drawings - Distributed Systems course 2014

click the shape that was initially in the middle canvas

the shape moves from middle canvas to the recycle bin canvas

click the shape that was initially in the left canvas

the shape moves back from middle to left canvas

click the ok button to approve a popup appears to notify that the branch has been resolved; the canvas did not change

click the check out button a popup appears to notify that there was no change from the server; the canvas is updated so that it looks exactly like the final state of the middle canvas (having only the shape that was in the right)

click the ok button to approve

click save a popup appears to notify that the branch master was saved; this means that the conflict was resolved from the server perspective too

click the ok button to approve

switch to the second client its canvas contains the two shapes as before

click the check out button a popup appears to notify that the branch has been updated

click the ok button to approve the branch with only one objects, as saved by the first client after merging, is shown

click the create branch button a popup appears to request a name, say A

write a different name and click the ok button to approve

the branch button A appeared close to the master (branch) button

click the create branch button a popup appears to request a name, say B

write a different name and click the ok button to approve

the branch button B appeared close to the master (branch) button

click the A (branch) button

click the check out button a popup appears to notify that the branch has been checked out

click the ok button to approve no change in the canvas as compared with master

draw a shape in the canvas the shape appears on the canvas

click the commit button a popup appears to notify that the change has been committed (locally, only on the client)

27

Page 28: Canva - magic mix drawings - Distributed Systems course 2014

click the B (branch) button

click the check out button a popup appears to notify that the branch has been checked out

click the ok button to approve no change in the canvas as compared with master

draw a shape in the canvas the shape appears on the canvas

click the commit button a popup appears to notify that the change has been committed (locally, only on the client)

click the ok button to approve

click the A (branch) button

click the merge button the user interface enters the merge screen; the three shapes look according to their origin: one active and one gray (inactive) on the left, one common in the middle, one active and one gray (inactive) on the right

click on the left active shape shape is moved from left to middle canvas

click on the right active shape shape is moved from right to middle canvas

click the ok button to approve a popup appears to notify that the branch merge has been resolved

click the ok button to approve

click the save button a popup appears to notify that the branch B was saved

click the ok button to approve

click the B (branch) button

click the check out button the resolved branch appears

switch to the first client the canvas is unchanged, and the B (branch) button appears

click the B (branch) button

click the check out button a popup appears to notify that the branch has been fetched

click the ok button to approve the canvas is populated as the B canvas was saved

click the A (branch) button

28

Page 29: Canva - magic mix drawings - Distributed Systems course 2014

click the delete button a popup appears to notify the delete

click the ok button to approve the B (branch) button disappeared

switch to the second client the B (branch) button disappeared

This scenario run uncovered two issues in notifying the client on delete. They have

been found in the server logs and fixed immediately.

2.2.5.4. Blocking test scenario

Performed action Expected result

connect with a first client there is no canvas in the user interface

click the master (branch) button

click the check out button a popup appears to notify that the branch has been fetched

click the ok button to approve a canvas appears in the upper part of the user interface; assume the canvas has no content

click on the block button a popup appears to notify that the branch master was blocked

click the ok button to approve

connect with a second client there is no canvas in the user interface

click the master (branch) button

click the check out button a popup appears to notify that the branch has been fetched

click the ok button to approve a canvas appears in the upper part of the user interface; assume the canvas has no content

draw a shape on the canvas the shape is drawn on the canvas

switch to the first client the shape can be seen as updated by the second client; this happens because now the branch is in blocked save state

switch to the second client

click the save button a popup appears to notify that the branch could not be saved; this is because the branch has been blocked by another client who also got a token to unblock

click the ok button to approve

29

Page 30: Canva - magic mix drawings - Distributed Systems course 2014

click the unblock button a popup appears to notify that the branch cannot be unblocked; this is because the branch has been blocked by another client who also got a token to unblock

click the ok button to approve

click the block button a popup appears to notify that the branch cannot be blocked; this is because the branch has been blocked by another client who also got a token to unblock

click the ok button to approve

switch to the first client

click the unblock button a popup appears to notify that the branch master was unblocked

click the ok button to approve

draw a shape on the canvas the shape is drawn on the canvas

switch to the second client the canvas does not include the last drawn shape; this is because the branch is not in blocked state anymore

draw a shape on the canvas the shape is drawn on the canvas

click the commit button a popup appears to notify that the change has been committed (locally, only on the client)

click the ok button to approve

click the save button a popup appears to notify that the branch master was saved

click the ok button to approve

switch to the first client

click the master (branch) button

click the check out button a popup appears to notify that the branch has been fetched

click the ok button to approve a canvas appears as it was saved by the other client

This scenario run did not uncover issues.

30

Page 31: Canva - magic mix drawings - Distributed Systems course 2014

We have performed brief checks on the application to see that the features are

functional and work as expected on Chrome 34, Internet Explorer 11 and Firefox 29.

3. Implementation

We wished to find technologies that fit the solution to the distributed branch based

collaborative canvas problem, allowed us to use our prior programming knowledge,

learn new concepts along the way, and make the application programming language

consistent. We therefore avoided a multitude of unrelated technologies, and went

with only one language for the entire stack. This allowed us learn how to design,

develop and test one feature, and then apply what we learnt on other features. The

source code can be seen in Appendix B.

3.1. Technologies

In the context of universally available application, and by using the prior knowledge

of one team member, we went with the safe and reliable Node.js stack. Throughout

the project, we were able to make proper use of many node modules and gather

them into a coherent and modular product.

3.1.1. Node.js web framework

Node.js [22] is a software platform on top of JavaScript [23]. This platform was the

choice for our project, due to its benefits as compared with different other platforms

[24] [25], in the context of our project. We chose Node.js for a number of reasons.

Node.js implementation on the server is done in JavaScript, which is the de facto

language for client side web applications. This way we were able to use a singular

programming language, and apply our skills for a module while developing others.

One of our group members already implemented several applications and has

knowledge about Node.js, and this would make it easy for the rest of the group

members to ramp up, towards a working level of understanding.

Node.js has active communities [22], resourceful tutorials and forums for discussion

. In the same way as the other platforms considered, it is easy to install multiple

modules [26] and use them right away. For our project in particular, we used jade

[28] for writing easier client HTML code, jQuery [28] for HTML element management,

underscore [29] for faster search and retrieval in lists, and socket [link] for client and

server communication. Node.js has support for testing, via the jasmine [jasmine]

module.

A Node.js applications can also be easily built and tested continuously with

Codeship [30], and deployed on Heroku [link]. All these facilities are provided free of

charge.

31

Page 32: Canva - magic mix drawings - Distributed Systems course 2014

3.1.2. Fabric.js canvas drawing framework

Fabric.js [31] is a JavaScript library which allows for easy creation and management of

objects drawn onto a canvas. It includes a few primitives, such as lines, circles,

rectangles and text boxes, which we implemented as modules in our project. We

used the provided event system, in order to construct the DFSM for each element.

The canvas comes decorated with events for object selection, movement, and

rotation, which would not come out of the box if we used the bare canvas HTML5

tag.

3.1.3. Socket.IO web communication framework

For our project, we needed to maintain persistent connection between the server

and the clients, that is able to send updates in real time, and be able to support

many clients at once.

Socket.io [32] is a JavaScript library for realtime web applications. It can be used

both client side in the browser, and server side in the node server. Socket.IO uses

the websocket protocol.

According to [32], Socket.IO does more than websocket, even if websocket is

selected as the transport and the user is browsing the website with a modern

browser. Certain features like heartbeats, timeouts and disconnection support are

vital to real time applications, but are not provided by the websocket API out of the

box.

According to [33], the Socket.IO stack runs on a single thread, which is also the

Node.js thread. This allows requests to be handled one by one, and discards the risk

of critical section inconsistencies when reading and writing the data structures

containing the participants and the branches on the server.

3.1.4. Jasmine.js test framework

Jasmine.js is a Node.js test framework for JavaScript. We decided to use Jasmine.js

to test our program because tests are written in JavaScript, the language of use in

our project, can be easily integrated into the continuous integration system. We

made use of tutorials [34], [35] and [36]. One of the group members had prior

knowledge of the framework and ramped up the team in using it.

3.2. Workflow

We have implemented this project in iterations. One iteration over the project

would have either added a new module, or would have integrated multiple modules.

3.2.1. Timeline

Before iterating the project, we ramped up and setup the repository, checked that

32

Page 33: Canva - magic mix drawings - Distributed Systems course 2014

everybody can contribute to the project and everybody was provided the resources

to learn JavaScript, Node, Fabric and the other technologies we decided to use. We

used an online space to share written and video resources, and also set meetings.

Our first iteration over the project consisted of developing a simple and working

chat system as a prototype, using the technologies at hand. At the end of the first

iteration, we setup the build and deploy systems.

Our second iteration consisted of developing an offline canvas, where a single user

could draw shapes on the canvas. During this iteration, we distributed our work for

developing modules.

In our third iteration, we integrated the offline canvas into a system similar to the

chat system, so that multiple clients could collaboratively draw shapes.

In the fourth iteration, we created a branch system independent from the canvas

and chat, by using our knowledge from the chat. The branch system used bare

numbers as objects, instead of real canvas states.

In the fifth iteration, we integrated the online canvas with the branch system, by

using the output of one as input to the other. We also wrote automated tests for the

server at this point.

In the sixth iteration, we split the blocked and unblocked states of branches, in

order to allow both real time collaboration on drawing, i.e, fetching all changes from

any client on that branch and update all clients, and classic repository workflow for

the collaboration on drawing, i.e., requiring the client to check out a branch in order

to get the latest changes. At the end of the iteration, we performed the manual

scenario tests presented above.

3.2.2. Tasks

We split our work into features and tasks. We used Trello [37] for organizing the

tasks in the project, and assigning tasks to team members. It helped us always have

an overview over the whole project, what was already done and what was still to be

done, and who was where at any point in time.

We created five columns: Features, To Do, Doing, Ready and Done. First, every

feature is in the Feature column. Features are broken down to one or more tasks put

in the To Do column. When one or more members starts solving a task, he or she

moves that specific task to the Doing column; when it is done, it will be moved to

the Ready column. At that point, the task is functional and working on his or her

feature branch, pending merge with the master branch. When the task is merged

onto the master, it is moved from Ready to Done.

For a better view of our effort areas, we created labels for tasks: development,

testing, integration, documentation, build and management. The Trello up to date

board can be found at [38].

33

Page 34: Canva - magic mix drawings - Distributed Systems course 2014

3.2.3. Repository

We needed a software for collaborative programming. It was important for each of us

to not interfere with the work of others, so we searched for a repository to work in

our own branches. We therefore decided to use a Git-based repository.

For our project we needed a source code hosting site that would allow us to create

and work in a private repository for free. We also wanted to be able to review or our

code at anytime. We considered a Git-based repository on GitHub [39] at first, but

unfortunately on GitHub you cannot have a private repository for free. We drawn our

attention on BitBucket [40], which also hosts Git-based repositories. Our repository

can be found at [41].

Not only it permits private free repositories, but also has easy features in the UI.

One of these is the pull request, which is used as a tool for code review. We used

this feature when the members could not meet in person. However, on the general

case, the team was cohesive enough to not have the problem of introducing faulty

code. Partly, this was possible due to the continuous integration system.

3.2.4. Build

We decided to try out a system in which we check in the code, and it appears in

“production”. This way we could quickly see how it looks, feels and runs on the web,

instead of our local repositories. Furthermore, it would help us run the tests

automatically with every check in. For this part, one team member had prior

knowledge of such products, while never setup a build and deploy system.

We found a very good build system Codeship [30], which is a continuous integration

and continuous deployment platform. It integrates BitBucket repositories and

Heroku deployed sites, and has easy support for Node.js. On the testing part, it

allowed any Node.j framework, in particular it worked with our choice Jasmine. In

order to run our tests on the platform, we made use of the tutorial at [42].

The continuous integration system had one drawback: it only had a limited number

of builds in the free version. For this reason, we had to make sure what we pushed

onto the repository was functional. We checked if the program was functional and

committed locally into our repositories, and only merged to master or pushed to

the server when we were feature complete.

3.2.5. Deployment

We used Heroku for hosting our website at [4]. Heroku is a cloud application

platform supporting several languages, including JavaScript, and the Node.js

framework. We found the logging feature [43] particularly useful one time, when the

live website crashed during testing.

34

Page 35: Canva - magic mix drawings - Distributed Systems course 2014

3.2.6. Test

The Jasmine framework is primarily a Node.js module, which made it a simple step

in the Codeship set of commands to build and deploy. Complete build and

deployment tasks, along with test results, can be found in Appendix C.

3.2.7. Team

At the beginning of the project, all three members contributed to idea selection,

repository setup and research, on both web framework and drawing libraries.

In the initial stages of the project, we worked together on getting a chat prototype

up and running. Here Vlad implemented the code, and all three members did test.

We also collaborated all together for the first drawing module, which was a circle.

Then we split work: Laura and Vlad worked on the rectangle, Laura worked with

Nanna on the text and line modules, Nanna took the ellipse and Laura took the

triangle. This feature was the second most complex feature in the project.

A side task to improve the canvas, which consisted of adding color and weight for

the stroke, and also integration, was done by Vlad and Laura. The integration of all

drawing modules onto the canvas and the canvas server integration was

implemented by Vlad and tested by all the team during development.

The most complex feature of the project, which consisted of the branching system,

was designed by Vlad with help from Laura, and implemented by both through pair

programming. The merge operations and the block token system were designed by

both and implemented by Vlad. All team members tested the system on Heroku

during the entire scope of the project.

The testing tasks, except continuous testing, were split as follows: Vlad and Laura

worked on the automated tests and designed the scenario tests. The build and

deployment system was setup by Vlad.

We decided to write the documentation iteratively, such that members write

chapters that are reviewed by other members. For the documentation editing,

Nanna had a key role in technology chapters, and all team members contributed to

the chapters on architecture and workflow. We wrote the introduction and the

conclusions together.

4. Conclusions

We have found a challenging and rewarding problem. We are happy to claim we

provide a viable solution for the need of collaborative drawing, which is useful for

many people.

Our application might prove useful for enterprise and students, while still being

suited for playful drawing. This is why we thought it as a great headstart for an open

35

Page 36: Canva - magic mix drawings - Distributed Systems course 2014

source project. We have a working repository, development, testing and deployment

system around it, which provide the infrastructure for a larger project. The project is

currently universally available.

Separating the workflow into multiple modules and tasks helped us not only plan our

work efficiently, but also have permanent knowledge of our status. We consider we

had a good approach in slicing the product into iterations. This allowed us to get

some features almost for free.

While not all team members have prior knowledge of versioning and branching, and

none has experience in developing a system with these features, we completed a

functional application by learning new things everyday. We were able to slice the

features such that we had a working product early.

Our initial plan was to develop a peer to peer and cloud backed application, yet we

realized that peer to peer does not solve this problem, and we did not have the

time resources to save data in the cloud. The future work would be focused on this,

and also on highly improved user experience and security.

The team members had the opportunity to improve their skills in designing and

implementing a distributed system, and get acquainted with a web communication

framework. All team members see this collaboration as a positive experience. There

were no dull moments, the project was interactive, and we enjoyed working

together.

List of references

1. Sabin Corneliu Buraga. Human Computer Interaction project proposal.

Alexandru Ioan Cuza University of Iasi. 2014

http://profs.info.uaic.ro/~busaco/teach/courses/hci/projects/index.html

2. Google. Drawings. 2014

https://docs.google.com/drawings/

3. Microsoft. OneNote. 2014

http://www.onenote.com/

4. Canva. Our project deployed on Heroku. 2014

http://canva.herokuapp.com/

5. Mark Pilgrim. Dive into HTML5. Let’s call it a draw(ing) interface. 2011

http://diveintohtml5.info/canvas.html

6. James Cope. QuickStudy: Peer-to-Peer network. 2002

http://www.computerworld.com/s/article/69883/Peer_to_Peer_Network

7. Java Programming Environment and the Java Runtime Environment. 2010

http://docs.oracle.com/cd/E19455-01/806-3461/6jck06gqd/index.html

36

Page 37: Canva - magic mix drawings - Distributed Systems course 2014

8. W3C. HTTP - Hypertext Transfer Protocol. 2003

http://www.w3.org/Protocols/

9. QuinStreet Webopedia. Rich Internet Application. 2014

http://www.webopedia.com/TERM/R/Rich_Internet_Application.html

10. Joyent, Inc.UDP / Datagram Sockets Node.js v0.10.28 Manual &

Documentation. 2014

http://nodejs.org/api/dgram.html

11. Kaazing Corporation. About HTML5 WebSockets. 2013

http://www.websocket.org/aboutwebsocket.html

12. Information Sciences Institute. University of Southern California.

Transmission Control Protocol. RFC 793. 1981

http://www.ietf.org/rfc/rfc793.txt

13. Mozilla Developer Network and individual contributors. Ajax. 2014

https://developer.mozilla.org/en/docs/AJAX

14. StackOverflow. Do HTML WebSockets maintain an open connection for each

client? Does this scale?. 2013

http://stackoverflow.com/questions/4852702/do-html-websockets-maintain-a

n-open-connection-for-each-client-does-this-scale

15. Peter Lubbers. Five Signs You Need HTML5 WebSockets. 2008

http://peterlubbers.sys-con.com/node/1551694/mobile

16. Software Freedom Conservancy. git. Retrieved 2014

http://git-scm.com/

17. Apache. Subversion. 2011

http://subversion.apache.org/

18. Mercurial. Retrieved 2014

http://mercurial.selenic.com/

19. Atlassian. Feature Branch Workflow. Retrieved 2014

https://www.atlassian.com/git/workflows#!workflow-feature-branch

20. William Mora. Node.js Tutorial - Building a Chatroom with Express.js +

Socket.IO. 2013

http://www.williammora.com/2013/03/nodejs-tutorial-building-chatroom-with

.html

21. StackOverflow. Load “Vanilla” Javascript Libraries into Node.js. 2011

http://stackoverflow.com/questions/5171213/load-vanilla-javascript-libraries-i

nto-node-js

37

Page 38: Canva - magic mix drawings - Distributed Systems course 2014

22. Joyent, Inc. node.js. Retrieved 2014

http://nodejs.org/about/

23. Mozilla Developer Network and individual contributors. JavaScript. 2014

https://developer.mozilla.org/en/docs/Web/JavaScript

24. Microsoft. ASP.net MVC Overview. 2014

http://www.asp.net/mvc/tutorials/older-versions/overview/asp-net-mvc-overv

iew

25. David Heinemeier Hansson. Ruby on Rails. Retrieved 2014

http://rubyonrails.org

26. Joyent, Inc. Node packaged modules. Retrieved 201

https://www.npmjs.org

27. Jade language. Retrieved 2014

http://jade-lang.com/

28. The JQuery foundation. jQuery. 2014

http://jquery.com/

29. Jeremy Ashkenas. underscore. GitHub. 2014

http://underscorejs.org/

30. Codeship. Retrieved 2014

https://www.codeship.io/

31. Juriy Zaytsev, Stefan Kienzle. Fabric.js. Retrieved 2014

http://fabricjs.com/

32. Guillermo Rauch. Socket.IO. Automattic. 2014

http://socket.io/

33. Codevate Limited. Developing a scalable real-time desktop or mobile

application with Socket.IO, Redis and HAProxy. 2013

http://www.codevate.com/blog/9-developing-a-scalable-real-time-desktop-or-

mobile-application-with-socketio-redis-and-haproxy

34. Rob Gravelle. Testing JavaScript Using the Jasmine Framework. 2014

http://www.htmlgoodies.com/beyond/javascript/testing-javascript-using-the-j

asmine-framework.html

35. Clemens Helm. Testing Tuesday #19: Testing node.js applications with

Jasmine. Video resource. Codeship. 2014

http://blog.codeship.io/2013/08/20/testing-tuesday-19-how-to-test-node-js-a

pplications-with-jasmine.html

36. Clemens Helm. Testing Tuesday #16: JavaScript Testing with Jasmine. Video

resource. Codeship. 2014

38

Page 39: Canva - magic mix drawings - Distributed Systems course 2014

http://blog.codeship.io/2013/07/30/testing-tuesday-16-javascript-testing-with

-jasmine.html

37. Fog Creek Software, Inc. Trello. 2014

https://trello.com/

38. Canva Trello. Our task board. 2014

https://trello.com/b/BEYARHxo/canvadtu

39. GitHub, Inc. GitHub. 2014

https://github.com/

40. Atlassian. Bitbucket. 2014

https://bitbucket.org/

41. Canva Bitbucket. Our repository. 2014

https://bitbucket.org/vladmanea/ds-project

42. Clemens Helm. Testing Tuesday #20: Continuous Deployment for node.js

applications. Video resource. Codeship. 2014

http://blog.codeship.io/2013/08/27/testing-tuesday-20-continuous-deployme

nt-for-node-js-applications.html

43. Heroku. Logging. Retrieved 2014

https://devcenter.heroku.com/articles/logging

Appendix A The other three proposed ideas are described below:

A.1. Piping command line API modules

It takes days for developers to mash up various API results from the social web, and

all it comes up with is IO and a bit of processing. We wanted to develop a

framework for developers to mash-up API results quicker, by using command line

modules. Developers can pipe command lines which retrieve and forward API data.

For example, piping the commands against the Twitter and Facebook modules, such

as twitter get last 10 tweets @vladmanea {title, content, time} |

facebook post all messages vlad.manea {title, body:content,

moment:utc(time)}would fetch the last 10 tweets from Vlad’s Twitter account, and

post each tweet as a message on Vlad’s Facebook account. The accolades represent

the attribute mappings, and the last attribute is a call of an arbitrarily implemented

function inside the Facebook module on the output of the Twitter module. Based on

this paradigm, should it prove beneficial, API modules can be implemented and

perfected by the community, just like Passport did with authentication providers.

39

Page 40: Canva - magic mix drawings - Distributed Systems course 2014

A.1.1. Strength

We can implement a powerful concept with cloud computation.

A.1.2. Weakness

While developing some sample modules, we might stumble upon programming

limits.

A.1.3. Opportunity

Make web developer life easy, and build up a community around our product.

A.1.4. Threat

The project might be related to distributed systems on a higher layer.

A.2. Google Docs LaTeX addon

Currently we have not seen a coherent, free collaborative editor for academic

documents. We wanted to develop an addon to be integrated with Google docs,

which would allow constructing LaTeX framework formulae. The Google Docs addon

technology is a new technology. For instance, a part of the document written in

LaTeX is collaboratively edited by more user, and then selected. A formula picture is

generated on the fly, and can be inserted in place. A nice to have would be making

use of the Google Docs version control to ensure undo back in LaTeX.

A.2.1. Strength

We can make use of a compiler in the Google Docs trustworthy environment.

A.2.2. Weakness

In this time frame not all formulae or functionality might be implemented.

A.2.3. Opportunity

Leverage the power of Google Docs for a more collaborative research world.

A.2.4. Threat

The project might relate to distributed systems on a higher layer.

A.3. Biker real time tracking

Going on a bike ride takes a lot of post-processing time. Current applications focus

on the competition and metrics after the fact only, which makes it a rather static

40

Page 41: Canva - magic mix drawings - Distributed Systems course 2014

process. We wish to develop a simple application for peer-to-peer finding riding

friends nearby, and fast and furious races with them. Based on the location of your

fellow riders (e.g., your Google+ contacts), we tell you how you stack up against

them in a race all along Strandvejen.

A.3.1. Strength

We can make use of geolocation to make it a fun experience.

A.3.2. Weakness

May take more time to prove the concept feasible in practice.

A.3.3. Opportunity

Ride healthy, make it a fun experience, and perhaps improve some records?

A.3.4. Threat

The project might relate to distributed systems on a higher layer.

Appendix B

B1. Server side code

package.json

{

"name": "Drawr",

"version": "0.0.1",

"description": "Drawr",

"main": "server.js",

"author": "Vlad Manea, Laura Stroe, Nanna Hjaltalin",

"dependencies": {

"socket.io": "0.9.16",

"express": "3.4.8",

"jade": "1.3.1",

"jquery": "1.11.0",

"stylus": "0.43.1",

"underscore": "1.6.0"

},

"engines": {

"node": "0.10.x",

"npm": "1.2.x"

},

"scripts": {

"start": "node server.js"

}

}

41

Page 42: Canva - magic mix drawings - Distributed Systems course 2014

app.js

var express = require('express');

var config = require('./config');

var routes = require('./routes');

var app = express();

if ('development' == app.get('env')) {

config.configDev(app);

}

if ('production' == app.get('env')) {

config.configProd(app);

}

module.exports = app;

chat.js

var _ = require('underscore');

function Chat() {

this.participants = [];

this.addParticipant = function(data) {

this.participants.push({id: data.id, name: data.name});

};

this.findParticipantById = function(participantId) {

return _.findWhere(this.participants, {id: participantId});

};

this.removeParticipant = function(participantId) {

this.participants = _.without(this.participants, _.findWhere(this.participants,

{id: participantId}));

};

};

Chat.prototype.newUser = function(data) {

this.addParticipant(data);

return {

status: 'OK',

participants: this.participants

};

};

Chat.prototype.nameChange = function(data, participantId) {

var participant = this.findParticipantById(participantId);

if (!participant) {

return {

status: 'ERROR',

situation: 'MISSING'

};

}

participant.name = data.name;

return {

status: 'OK'

};

42

Page 43: Canva - magic mix drawings - Distributed Systems course 2014

};

Chat.prototype.disconnect = function(participantId) {

this.removeParticipant(participantId);

return {

status: 'OK'

};

};

module.exports = Chat;

config.js

var path = require('path');

var express = require('express');

// The configuration data.

exports.data = {

// Server configuration.

server: {

port: process.env.PORT || 5000

},

// Security configuration (not used for now).

security: {

cookieParserSecret: 'Secret'

},

// Route configuration.

route: {

path: path.join(__dirname, 'routes')

},

// Controller configuration.

controller: {

path: path.join(__dirname, 'controllers')

},

// Model configuration.

model: {

path: path.join(__dirname, 'models')

},

// View configuration.

view: {

path: path.join(__dirname, 'views'),

engine: 'jade'

},

// Style configuration.

style: {

path: path.join(__dirname, 'public'),

engine: 'stylus'

}

}

function configure(app) {

// Common section.

app.set('port', exports.data.server.port);

43

Page 44: Canva - magic mix drawings - Distributed Systems course 2014

app.set('views', exports.data.view.path);

app.set('view engine', exports.data.view.engine);

app.use(express.logger('dev'));

app.use(express.json());

app.use(express.urlencoded());

app.use(express.cookieParser(exports.data.security.cookieParserSecret));

app.use(express.session());

app.use(app.router);

app.use(require(exports.data.style.engine).middleware(exports.data.style.path));

app.use(express.static(exports.data.style.path));

}

// The configuration function for dev.

exports.configDev = function(app) {

configure(app);

app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));

};

// The configuration function for prod.

exports.configProd = function(app) {

configure(app);

app.use(express.errorHandler());

};

http.js

var http = require('http');

var app = require('./app');

var server = http.createServer(app);

server.listen(app.get('port'));

module.exports = server;

io.js

var _ = require('underscore');

var http = require('./http');

var io = require('socket.io').listen(http);

var Repository = require('./repository');

var repository = new Repository();

var Chat = require('./chat');

var chat = new Chat();

// Socket events

io.sockets.on('connection', function (socket) {

// User related

socket.on("newUser", function(data) {

var result = chat.newUser(data);

if (result.status == 'OK') {

io.sockets.emit("newConnection", {participants: result.participants});

socket.emit("updateBranchCreated", repository.getActiveBranches());

}

});

socket.on("nameChange", function(data) {

var result = chat.nameChange(data, socket.id);

44

Page 45: Canva - magic mix drawings - Distributed Systems course 2014

if (result.status == 'OK') {

io.sockets.emit("nameChanged", {id: data.id, name: data.name});

return;

}

if (result.status == 'ERROR') {

switch (result.situation) {

case 'MISSING':

socket.emit('error', {message: 'Could not change name for missing

participant.'});

break;

}

}

});

socket.on("disconnect", function() {

var result = chat.disconnect(socket.id);

if (result.status == 'OK') {

io.sockets.emit("userDisconnected", {id: socket.id, sender: "system"});

}

});

// Figure related

socket.on("newFigure", function(data) {

if (data.branch && repository.isBlocked(data.branch.name)) {

io.sockets.emit("figureCreated", {id: socket.id, canvas: data.canvas, branch:

data.branch});

}

});

// Branch related

socket.on("block", function(data) {

var result = repository.block(data);

if (result.status == 'OK') {

socket.emit("blocked", {

branch: {

name: result.branch.name

},

token: result.token

});

return;

}

if (result.status == 'ERROR') {

switch (result.situation) {

case 'INVALID':

socket.emit('error', {message: 'Invalid request'});

break;

case 'MISSING':

socket.emit('error', {message: 'Could not find branch ' +

data.name});

break;

case 'INUSE':

socket.emit('error', {message: 'Could not block branch. There is

another participant in control.'});

break;

}

45

Page 46: Canva - magic mix drawings - Distributed Systems course 2014

}

});

socket.on("unblock", function(data) {

var result = repository.unblock(data);

if (result.status == 'OK') {

socket.emit("unblocked", {

branch: {

name: result.branch.name

}

});

return;

}

if (result.status == 'ERROR') {

switch (result.situation) {

case 'INVALID':

socket.emit('error', {message: 'Invalid request'});

break;

case 'MISSING':

socket.emit('error', {message: 'Could not find branch ' +

data.name});

break;

case 'FORBIDDEN':

socket.emit('error', {message: 'Could not unblock branch. There is

another participant in control.'});

break;

}

}

});

socket.on("checkOutBranch", function(data) {

var result = repository.checkOutBranch(data);

if (result.status == 'OK') {

socket.emit("branchCheckedOut", result.branch);

return;

}

if (result.status == 'ERROR') {

switch (result.situation) {

case 'INVALID':

socket.emit('error', {message: 'Invalid request'});

break;

case 'MISSING':

socket.emit('error', {message: 'Could not find branch ' +

data.name});

break;

}

}

});

socket.on("saveBranch", function(data) {

var result = repository.saveBranch(data);

if (result.status == 'OK') {

switch (result.situation) {

case 'CREATED':

io.sockets.emit("updateBranchCreated",

repository.getActiveBranches());

break;

46

Page 47: Canva - magic mix drawings - Distributed Systems course 2014

case 'SAVED':

socket.emit("branchSaved", {name: data.branch.name, automatic:

data.automatic});

break;

}

return;

}

if (result.status == 'WARNING') {

switch (result.situation) {

case 'UPTODATE':

socket.emit("warning", {message: "Did not save branch. Branch is

already up to date."});

break;

}

return;

}

if (result.status == 'ERROR') {

switch (result.situation) {

case 'INVALID':

socket.emit("error", {message: "Invalid request."});

break;

case 'MISSING':

socket.emit("error", {message: "Could not find branch " +

data.branch.name + "."});

break;

case 'CLIENTHEAD':

socket.emit("error", {message: "Client error, branch head was not

created."});

break;

case 'SERVERHEAD':

socket.emit("error", {message: "Server error, branch head was not

created."});

break;

case 'CONFLICT':

socket.emit("error", {message: "Could not save branch due to

another save. Checkout branch to get latest changes."});

break;

case 'FORBIDDEN':

socket.emit("error", {message: "Could not save branch. There is

another participantincontrol.Youmaytrytoblockthebranchifithasbeenblockedfor

a long time."});

break;

}

}

});

socket.on("deleteBranch", function(data) {

var result = repository.deleteBranch(data);

if (result.status == 'OK') {

socket.emit("branchDeleted", data.name);

// Remove the branch from the available branches in the client.

io.sockets.emit("updateBranchDeleted", data.name);

return;

}

if (result.status == 'ERROR') {

47

Page 48: Canva - magic mix drawings - Distributed Systems course 2014

switch (result.situation) {

case 'INVALID':

socket.emit("error", {message: "Invalid request."});

break;

case 'MISSING':

socket.emit("error", {message: "Could notfindbranch"+ data.name

+ "."});

break;

case 'MASTER':

socket.emit("error", {message: "Cannot delete branch master."});

break;

}

}

});

});

module.exports = io;

repository.js

var _ = require('underscore');

var TIMEOUT_SECONDS = 300;

function Repository() {

this.branches = [];

this.branches.push({

name: 'master',

nodes: [{

id : {

numeric: 0,

author: {},

branch: 'master'

},

object: {objects: [], background: ""},

next: {},

saved: true

}],

active: true

});

this.blockades = [];

this.findBranchByName = function(branchName) {

return _.find(this.branches, function(branch) { return branch.name ==

branchName; });

};

this.removeBranch = function(branchName) {

this.branches = _.filter(this.branches, function(branch) { return branch.name

!= branchName; });

};

this.addBranch = function(branch) {

this.branches.push(branch);

};

this.updateBranch = function(branch) {

this.removeBranch(branch.name);

this.addBranch(branch);

};

48

Page 49: Canva - magic mix drawings - Distributed Systems course 2014

this.markSavedBranch = function(branch) {

for (var index = branch.nodes.length - 1; index >= 0; --index) {

if (branch.nodes[index].saved == false) {

branch.nodes[index].saved = true;

} else {

return;

}

}

};

this.removeBlockade = function(branchName) {

this.blockades = _.filter(this.blockades, function(blockade) { return

blockade.name != branchName; });

};

this.addBlockade = function(blockade) {

this.blockades.push(blockade);

};

this.findBlockade = function(branchName) {

return _.find(this.blockades, function(blockade) { return blockade.name ==

branchName; });

};

};

Repository.prototype.isBlocked = function(branchName) {

return this.findBlockade(branchName);

};

Repository.prototype.block = function(data) {

if (!data || !data.participant || !data.participant.id || !data.branch ||

!data.branch.name) {

return {

status: 'ERROR',

situation: 'INVALID'

};

}

// See if the branch exists

var branchName = data.branch.name;

var foundBranch = this.findBranchByName(branchName);

if (!foundBranch || foundBranch.active == false) {

return {

status: 'ERROR',

situation: 'MISSING'

};

}

var now = new Date();

var participantId = data.participant.id;

var blockade = this.findBlockade(branchName);

if (blockade) {

// Check that the blocking time has expired

if (new Date(blockade.date.getTime() + TIMEOUT_SECONDS * 1000) < now) {

// This user can take control, and is given a replace token

blockade.participantId = participantId;

var token = Math.random();

blockade.token = token;

blockade.date = now;

49

Page 50: Canva - magic mix drawings - Distributed Systems course 2014

return {

status: 'OK',

branch: {

name: branchName

},

token: token

};

}

var token = data.token;

if (!token || blockade.token != token) {

// This user has not authenticated

return {

status: 'ERROR',

situation: 'INUSE'

};

}

// This user can prolong his blocking control, and is given a new token

blockade.date = now;

blockade.token = Math.random();

return {

status: 'OK',

branch: {

name: branchName

},

token: blockade.token

};

}

// This user can take control, and is given an initial token

var token = Math.random();

this.addBlockade({

name: branchName,

participantId: participantId,

token: token,

date: now

});

return {

status: 'OK',

branch: {

name: branchName

},

token: token

};

};

Repository.prototype.unblock = function(data) {

if (!data || !data.participant || !data.participant.id || !data.branch ||

!data.branch.name) {

return {

status: 'ERROR',

situation: 'INVALID'

};

}

if (!data.token) {

return {

status: 'ERROR',

50

Page 51: Canva - magic mix drawings - Distributed Systems course 2014

situation: 'FORBIDDEN'

};

}

// See if the branch exists

var branchName = data.branch.name;

var foundBranch = this.findBranchByName(branchName);

if (!foundBranch || foundBranch.active == false) {

return {

status: 'ERROR',

situation: 'MISSING'

};

}

var participantId = data.participant.id;

var token = data.token;

var blockade = this.findBlockade(branchName);

if (blockade && blockade.token != token) {

return {

status: 'ERROR',

situation: 'FORBIDDEN'

};

}

this.removeBlockade(branchName);

return {

status: 'OK',

branch: {

name: branchName

}

};

};

Repository.prototype.getActiveBranches = function() {

return _.where(this.branches, {active: true});

};

Repository.prototype.checkOutBranch = function(data) {

if (!data || !data.name) {

return {

status: 'ERROR',

situation: 'INVALID'

};

}

var branchName = data.name;

var foundBranch = this.findBranchByName(branchName);

if (!foundBranch || foundBranch.active == false) {

return {

status: 'ERROR',

situation: 'MISSING'

};

}

return {

status: 'OK',

branch: foundBranch

};

};

Repository.prototype.saveBranch = function(data) {

if (!data || !data.branch || !data.branch.name) {

51

Page 52: Canva - magic mix drawings - Distributed Systems course 2014

return {

status: 'ERROR',

situation: 'INVALID'

};

}

var branchName = data.branch.name;

var foundBranch = this.findBranchByName(branchName);

if (!foundBranch) {

// I. Branch was not saved before => just save it now.

this.markSavedBranch(data.branch);

this.updateBranch(data.branch);

return {

status: 'OK',

situation: 'CREATED'

};

}

if (foundBranch.active == false) {

return {

status: 'ERROR',

situation: 'MISSING'

};

}

var blockade = this.findBlockade(branchName);

if (blockade) {

var token = data.token;

if (!token || blockade.token != token) {

return {

status: 'ERROR',

situation: 'FORBIDDEN'

};

}

}

var serverBranch = foundBranch;

var clientBranch = data.branch;

if (clientBranch.nodes.length <= 0) {

return {

status: 'ERROR',

situation: 'CLIENTHEAD'

};

}

if (serverBranch.nodes.length <= 0) {

return {

status: 'ERROR',

situation: 'SERVERHEAD'

};

}

var min = Math.min(clientBranch.nodes.length, serverBranch.nodes.length);

if (clientBranch.nodes.length > min) {

if (JSON.stringify(clientBranch.nodes[min - 1].id) ==

JSON.stringify(serverBranch.nodes[min - 1].id)) {

// II.2. Client made changes, server did not => add change to branch.

this.markSavedBranch(data.branch);

52

Page 53: Canva - magic mix drawings - Distributed Systems course 2014

this.updateBranch(data.branch);

return {

status: 'OK',

situation: 'SAVED'

};

}

// II.4. Both made changes, reject change.

return {

status: 'ERROR',

situation: 'CONFLICT'

};

}

if (serverBranch.nodes.length > min) {

if (JSON.stringify(clientBranch.nodes[min - 1].id) ==

JSON.stringify(serverBranch.nodes[min - 1].id)) {

// II.3. Server made changes, client did not => do nothing.

return {

status: 'WARNING',

situation: 'UPTODATE'

};

}

// II.4. Both made changes, reject change.

return {

status: 'ERROR',

situation: 'CONFLICT'

};

}

if (JSON.stringify(clientBranch.nodes[min - 1].id) ==

JSON.stringify(serverBranch.nodes[min - 1].id)) {

// II.1. Nobody made changes => do nothing.

return {

status: 'WARNING',

situation: 'UPTODATE'

};

}

return {

status: 'ERROR',

situation: 'CONFLICT'

};

};

Repository.prototype.deleteBranch = function(data) {

if (!data || !data.name) {

return {

status: 'ERROR',

situation: 'INVALID'

};

}

var branchName = data.name;

if (branchName == 'master') {

return {

status: 'ERROR',

situation: 'MASTER'

};

53

Page 54: Canva - magic mix drawings - Distributed Systems course 2014

}

var foundBranch = this.findBranchByName(branchName);

if (!foundBranch || foundBranch.active == false) {

return {

status: 'ERROR',

situation: 'MISSING'

};

}

// Deactivate the branch.

foundBranch.active = false;

return {

status: 'OK'

};

};

module.exports = Repository;

routes.js

var _ = require('underscore');

module.exports.configure = function(app, io) {

// GET method to obtain the index view

app.get("/", function(request, response) {

response.render("index");

});

// POST method to create a chat message

app.post("/message", function(request, response) {

var message = request.body.message;

if(_.isUndefined(message) || _.isEmpty(message.trim())) {

return response.json(400, {error: "Message is invalid"});

}

var name = request.body.name;

io.sockets.emit("incomingMessage", {message: message, name: name});

response.json(200, {message: "Message received"});

});

}

server.js

var app = require('./app');

var io = require('./io');

var routes = require('./routes');

routes.configure(app, io);

B.2. Client side code

circle.js

function CircleDrawer(canvas, modifier, socket) {

var iniPos;

54

Page 55: Canva - magic mix drawings - Distributed Systems course 2014

var previewCircle;

var pressed = false;

this.mouseDown = function(options) {

iniPos = { x: options.e.clientX, y: options.e.clientY };

pressed = true;

};

this.mouseMove = function(options) {

if (!pressed) {

return;

}

var radius = Math.sqrt(Math.pow(options.e.clientX - iniPos.x, 2) +

Math.pow(options.e.clientY - iniPos.y, 2));

canvas.remove(previewCircle);

previewCircle = new fabric.Circle({

strokeWidth: modifier.get('preview-stroke-width'),

stroke: modifier.get('preview-stroke-color'),

fill: modifier.get('preview-fill-color'),

left: iniPos.x - radius,

top: iniPos.y - radius,

radius: radius

});

canvas.add(previewCircle);

};

this.mouseUp = function(options) {

pressed = false;

var radius = Math.sqrt(Math.pow(options.e.clientX - iniPos.x, 2) +

Math.pow(options.e.clientY - iniPos.y, 2));

canvas.remove(previewCircle);

if (iniPos.x == options.e.clientX && iniPos.x == options.e.clientX) {

return;

}

var circle = new fabric.Circle({

strokeWidth: modifier.get('actual-stroke-width'),

stroke: modifier.get('actual-stroke-color'),

fill: modifier.get('actual-fill-color'),

left: iniPos.x - radius,

top: iniPos.y - radius,

radius: radius

});

canvas.add(circle);

socket.figureCreated(canvas);

};

};

ellipse.js

function EllipseDrawer(canvas, modifier, socket) {

var pressed = false;

var previewEllipse;

var iniPos;

this.mouseDown = function(options) {

55

Page 56: Canva - magic mix drawings - Distributed Systems course 2014

iniPos = { x: options.e.clientX, y: options.e.clientY };

pressed = true;

};

this.mouseMove = function(options) {

if (!pressed) {

return;

}

canvas.remove(previewEllipse);

var radiusx = Math.abs(options.e.clientX - iniPos.x);

var radiusy = Math.abs(options.e.clientY - iniPos.y);

previewEllipse = new fabric.Ellipse({

left:iniPos.x - radiusx,

top: iniPos.y - radiusy,

strokeWidth: modifier.get('preview-stroke-width'),

stroke: modifier.get('preview-stroke-color'),

fill: modifier.get('preview-fill-color'),

rx: radiusx,

ry: radiusy

});

canvas.add(previewEllipse);

};

this.mouseUp = function(options) {

pressed = false;

canvas.remove(previewEllipse);

var radiusx = Math.abs(options.e.clientX - iniPos.x);

var radiusy = Math.abs(options.e.clientY - iniPos.y);

var ellipse = new fabric.Ellipse({

left: iniPos.x - radiusx,

top: iniPos.y - radiusy,

strokeWidth: modifier.get('actual-stroke-width'),

stroke: modifier.get('actual-stroke-color'),

fill: modifier.get('actual-fill-color'),

rx: radiusx,

ry: radiusy

});

canvas.add(ellipse);

socket.figureCreated(canvas);

};

};

line.js

function LineDrawer(canvas, modifier, socket) {

var pressed = false;

var previewLine;

var iniPos;

this.mouseDown = function(options) {

iniPos = { x1: options.e.clientX, y1: options.e.clientY };

pressed = true;

};

56

Page 57: Canva - magic mix drawings - Distributed Systems course 2014

this.mouseMove = function(options) {

if (!pressed) {

return;

}

canvas.remove(previewLine);

previewLine = new fabric.Line([iniPos.x1, iniPos.y1, options.e.clientX,

options.e.clientY], {

strokeWidth: modifier.get('preview-stroke-width'),

stroke: modifier.get('preview-stroke-color'),

});

canvas.add(previewLine);

};

this.mouseUp = function(options) {

pressed = false;

canvas.remove(previewLine);

var line = new fabric.Line([iniPos.x1, iniPos.y1, options.e.clientX,

options.e.clientY], {

strokeWidth: modifier.get('actual-stroke-width'),

stroke: modifier.get('actual-stroke-color'),

});

canvas.add(line);

socket.figureCreated(canvas);

};

};

polyline.js

function PolylineDrawer(canvas, modifier, socket) {

var points = [];

var previewLines = [];

this.mouseUp = function(options) {

points.push({x: options.e.clientX, y: options.e.clientY});

if (points.length <= 1) {

return;

}

var point1 = points[points.length - 2];

var point2 = points[points.length - 1];

var previewLine = new fabric.Line([point1.x, point1.y, point2.x, point2.y],{

strokeWidth: modifier.get('preview-stroke-width'),

stroke: modifier.get('preview-stroke-color')

});

previewLines.push(previewLine);

canvas.add(previewLine);

};

this.stateChanged = function() {

for (var index = 0; index < previewLines.length; ++index) {

var previewLine = previewLines[index];

57

Page 58: Canva - magic mix drawings - Distributed Systems course 2014

canvas.remove(previewLine);

canvas.add(new fabric.Line([previewLine.x1, previewLine.y1,

previewLine.x2, previewLine.y2], {

strokeWidth: modifier.get('actual-stroke-width'),

stroke: modifier.get('actual-stroke-color'),

}));

}

socket.figureCreated(canvas);

while (previewLines.length > 0) {

previewLines.pop();

}

}

};

rectangle.js

function RectangleDrawer(canvas, modifier, socket) {

var pressed = false;

var previewRect;

var iniPos;

this.mouseDown = function(options) {

iniPos = { x: options.e.clientX, y: options.e.clientY };

pressed = true;

};

this.mouseMove = function(options) {

if (!pressed) {

return;

}

canvas.remove(previewRect);

previewRect = new fabric.Rect({

left: iniPos.x,

top: iniPos.y,

strokeWidth: modifier.get('preview-stroke-width'),

stroke: modifier.get('preview-stroke-color'),

fill: modifier.get('preview-fill-color'),

width: options.e.clientX - iniPos.x,

height: options.e.clientY - iniPos.y

});

canvas.add(previewRect);

};

this.mouseUp = function(options) {

pressed = false;

canvas.remove(previewRect);

var rect = new fabric.Rect({

left: iniPos.x,

top: iniPos.y,

strokeWidth: modifier.get('actual-stroke-width'),

stroke: modifier.get('actual-stroke-color'),

fill: modifier.get('actual-fill-color'),

width: options.e.clientX - iniPos.x,

height: options.e.clientY - iniPos.y

});

58

Page 59: Canva - magic mix drawings - Distributed Systems course 2014

canvas.add(rect);

socket.figureCreated(canvas);

};

};

text.js

function TextDrawer(canvas, modifier, socket) {

this.mouseUp = function(options) {

var text = prompt("Enter text"," ");

if (text != null) {

var textbox = new fabric.Text(text, {

left: options.e.clientX,

top: options.e.clientY

});

canvas.add(textbox);

socket.figureCreated(canvas);

}

};

};

triangle.js

function TriangleDrawer(canvas, modifier, socket) {

var pressed = false;

var previewTriangle;

var iniPos;

this.mouseDown = function(options) {

iniPos = { x: options.e.clientX, y: options.e.clientY };

pressed = true;

};

this.mouseMove = function(options) {

if (!pressed) {

return;

}

canvas.remove(previewTriangle);

previewTriangle = new fabric.Triangle({

left: iniPos.x,

top: iniPos.y,

strokeWidth: modifier.get('preview-stroke-width'),

stroke: modifier.get('preview-stroke-color'),

fill: modifier.get('preview-fill-color'),

width: options.e.clientX - iniPos.x,

height: options.e.clientY - iniPos.y

});

canvas.add(previewTriangle);

};

this.mouseUp = function(options) {

pressed = false;

canvas.remove(previewTriangle);

if (iniPos.x == options.e.clientX && iniPos.x == options.e.clientX) {

59

Page 60: Canva - magic mix drawings - Distributed Systems course 2014

return;

}

var triangle = new fabric.Triangle({

left: iniPos.x,

top: iniPos.y,

strokeWidth: modifier.get('actual-stroke-width'),

stroke: modifier.get('actual-stroke-color'),

fill: modifier.get('actual-fill-color'),

width: options.e.clientX - iniPos.x,

height: options.e.clientY - iniPos.y

});

canvas.add(triangle);

socket.figureCreated(canvas);

};

};

chat.js

function Chat() {

this.sessionId = '';

};

Chat.prototype.getSessionId = function() {

return this.sessionId;

}

Chat.prototype.setSessionId = function(newSessionId) {

this.sessionId = newSessionId;

}

index.js

$(document).on('ready', function() {

$('#merge').hide();

$('#workspace').hide();

var canvas = new fabric.Canvas('demo');

var modifier = new Modifier();

setupModifier(modifier);

setupModifierButtonEvents(modifier);

var painter = new Painter(canvas);

var chat = new Chat();

var ui = new UI();

var repository = new Repository(ui);

var socket = new Socket(document.domain, canvas, chat, repository, ui);

registerFigures(canvas, painter, modifier, socket);

registerFigureButtonEvents(painter);

registerCanvasEvents(canvas, painter);

setupChatControls(socket, ui);

setupBranchButtons(socket, canvas);

});

function setupModifier(modifier) {

60

Page 61: Canva - magic mix drawings - Distributed Systems course 2014

// Specify preview shape attributes

modifier.set('preview-stroke-width', 10);

modifier.set('preview-stroke-color', 'gray');

modifier.set('preview-fill-color', 'lightgray');

// Specify actual attributes

modifier.set('actual-stroke-width', 10);

modifier.set('actual-stroke-color', 'black');

modifier.set('actual-fill-color', 'transparent');

};

function registerFigures(canvas, painter, modifier, socket) {

painter.registerDrawer('circle', new CircleDrawer(canvas, modifier, socket));

painter.registerDrawer('ellipse', new EllipseDrawer(canvas, modifier, socket));

painter.registerDrawer('rectangle', new RectangleDrawer(canvas, modifier,

socket));

painter.registerDrawer('triangle', new TriangleDrawer(canvas, modifier, socket));

painter.registerDrawer('line', new LineDrawer(canvas, modifier, socket));

painter.registerDrawer('polyline', new PolylineDrawer(canvas, modifier, socket));

painter.registerDrawer('text', new TextDrawer(canvas, modifier, socket));

};

function registerFigureButtonEvents(painter) {

$('#circle').on('click', function() { painter.setDrawer('circle') });

$('#ellipse').on('click', function() { painter.setDrawer('ellipse') });

$('#rectangle').on('click', function() { painter.setDrawer('rectangle') });

$('#triangle').on('click', function() { painter.setDrawer('triangle') });

$('#line').on('click', function() { painter.setDrawer('line') });

$('#polyline').on('click', function() { painter.setDrawer('polyline') });

$('#text').on('click', function() { painter.setDrawer('text') });

};

function registerCanvasEvents(canvas, painter) {

canvas.on('mouse:down', function(options) { painter.mouseDown(options); });

canvas.on('mouse:move', function(options) { painter.mouseMove(options); });

canvas.on('mouse:up', function(options) { painter.mouseUp(options); });

canvas.on('object:moving', function(options) { painter.clearDrawer(); });

canvas.on('object:scaling', function(options) { painter.clearDrawer(); });

canvas.on('object:rotating', function(options) { painter.clearDrawer(); });

canvas.on('object:modified', function(options) { painter.clearDrawer(); });

};

function setupModifierButtonEvents(modifier) {

// Setup the stroke color picker

$('#strokeColor').colorpicker();

$('#strokeColor').colorpicker().on('changeColor', function(ev) {

var color = ev.color.toRGB();

modifier.set('actual-stroke-color', 'rgba(' + color.r + ', ' + color.g + ', ' +

color.b + ', ' + color.a + ')');

});

// Setup the fill color picker

$('#fillColor').colorpicker();

$('#fillColor').colorpicker().on('changeColor', function(ev) {

var color = ev.color.toRGB();

modifier.set('actual-fill-color', 'rgba(' + color.r + ', ' + color.g + ', ' +

color.b + ', ' + color.a + ')');

});

// Setup the stroke size slider

61

Page 62: Canva - magic mix drawings - Distributed Systems course 2014

$("#strokeWidth").change(function() {

modifier.set('preview-stroke-width', $(this).val());

modifier.set('actual-stroke-width', $(this).val());

});

};

function setupChatControls(socket, ui) {

$('#outgoingMessage').on('keydown', ui.outgoingMessageKeyDown);

$('#outgoingMessage').on('keyup', ui.outgoingMessageKeyUp);

$('#name').on('focusout', socket.nameFocusOut);

$('#send').on('click', ui.sendMessage);

};

function setupBranchButtons(socket, canvas) {

$('#checkOutBranch').on('click', function() {

var name = $('#branches button.selected').html();

if (name) {

socket.checkOutBranch(name);

} else {

alert("You did not select a branch from the list.");

}

});

$('#createBranch').on('click', function() {

var name = prompt("Please enter a branch name", "");

if (name) {

socket.createBranch(name);

} else {

alert("You did not specify a name.");

}

});

$('#deleteBranch').on('click', function() {

var name = $('#branches button.selected').html();

if (name) {

socket.deleteBranch(name);

} else {

alert("You did not select a branch from the list.");

}

});

$('#commitBranch').on('click', function() {

var obj = canvas.toDatalessJSON();

socket.commitBranch(obj, false);

});

$('#mergeBranch').on('click', function() {

var name = $('#branches button.selected').html();

if (name) {

socket.mergeBranch(name);

} else {

alert("You did not select a branch from the list.");

}

});

$('#saveBranch').on('click', function() {

socket.saveBranch();

});

$('#block').on('click', function() {

socket.block();

});

62

Page 63: Canva - magic mix drawings - Distributed Systems course 2014

$('#unblock').on('click', function() {

socket.unblock();

});

}

modifier.js

function Modifier(canvas) {

var properties = [];

this.get = function(propertyName) {

return properties[propertyName];

}

this.set = function(propertyName, propertyValue) {

properties[propertyName] = propertyValue;

}

};

painter.js

function Painter(canvas) {

// Register drawer types

var currentDrawerName;

var drawers = [];

this.registerDrawer = function(drawerName, drawer) {

drawers[drawerName] = drawer;

}

this.setDrawer = function(drawerName) {

if (!drawers[drawerName]) {

throw "Illegal argument exception: " + drawerName + " is not registered!";

}

for (var drawer in drawers) {

if (!drawers[drawer].stateChanged) {

continue;

}

drawers[drawer].stateChanged();

}

currentDrawerName = drawerName;

}

this.clearDrawer = function() {

currentDrawerName = "";

}

this.mouseDown = function(options) {

if (!drawers[currentDrawerName]) {

return;

}

if (!drawers[currentDrawerName].mouseDown) {

return;

}

63

Page 64: Canva - magic mix drawings - Distributed Systems course 2014

drawers[currentDrawerName].mouseDown(options);

}

this.mouseMove = function(options) {

if (!drawers[currentDrawerName]) {

return;

}

if (!drawers[currentDrawerName].mouseMove) {

return;

}

drawers[currentDrawerName].mouseMove(options);

}

this.mouseUp = function(options) {

if (!drawers[currentDrawerName]) {

return;

}

if (!drawers[currentDrawerName].mouseUp) {

return;

}

drawers[currentDrawerName].mouseUp(options);

}

};

repository.js

function Repository(ui) {

this.ui = ui;

this.branches = [];

this.blockades = [];

this.clientBranch;

this.removeBlockade = function(branchName) {

this.blockades = _.filter(this.blockades, function(blockade) { return

blockade.name != branchName; });

};

this.addBlockade = function(blockade) {

this.blockades.push(blockade);

};

this.findBlockade = function(branchName) {

return _.find(this.blockades, function(blockade) { return blockade.name ==

branchName; });

};

this.removeBranch = function(branchName) {

this.branches = _.filter(this.branches, function(branch) { return branch.name

!= branchName; });

};

this.addBranch = function(branch) {

this.branches.push(branch);

}

};

Repository.prototype.getBranches = function() {

64

Page 65: Canva - magic mix drawings - Distributed Systems course 2014

return this.branches;

};

Repository.prototype.getBranch = function() {

return this.clientBranch;

};

Repository.prototype.getBranchByName = function(branchName) {

return _.find(this.branches, function(branch) { return branch.name ==

branchName; });

};

Repository.prototype.getNode = function() {

return this.clientBranch.nodes[this.clientBranch.nodes.length - 1];

}

Repository.prototype.getValue = function() {

if (!this.clientBranch) {

return {objects: [], background: ""};

}

return this.getNode().object;

};

Repository.prototype.createBranch = function(branchName) {

if (!this.clientBranch) {

return {

status: 'ERROR',

situation: 'CHECKOUT'

};

}

var existingBranch = this.getBranchByName(branchName);

if (existingBranch) {

return {

status: 'ERROR',

situation: 'RENAME'

};

}

// Restrict branch creation to saved this.branches only.

var motherNode = this.getNode();

if (motherNode.saved == false) {

return {

status: 'ERROR',

situation: 'SAVE'

};

}

var branch = {

name: branchName,

active: true,

nodes: [{

id: {

numeric: 0,

author: {

id: this.ui.getUserId(),

name: this.ui.getUserName()

},

branch: branchName

},

object: motherNode.object,

65

Page 66: Canva - magic mix drawings - Distributed Systems course 2014

next: motherNode,

saved: false

}]

};

this.addBranch(branch);

return {

status: 'OK',

branch: branch

};

};

Repository.prototype.commitBranch = function(objectToCommit) {

if (!this.clientBranch) {

return {

status: 'ERROR',

situation: 'CHECKOUT'

};

}

this.clientBranch.nodes.push({

id: {

numeric: this.getNode().id.numeric + 1,

author: {

id: this.ui.getUserId(),

name: this.ui.getUserName()

},

branch: this.clientBranch.name

},

object: objectToCommit,

next: this.getNode(),

saved: false

});

return {

status: 'OK'

};

};

Repository.prototype.updateBranch = function(branch) {

this.removeBranch(branch.name);

this.addBranch(branch);

this.useBranch(branch);

};

Repository.prototype.useBranch = function(branch) {

this.clientBranch = branch;

};

Repository.prototype.revertCurrentBranch = function() {

if (!this.clientBranch) {

return;

}

for (var index = this.clientBranch.nodes.length - 1; index > 0; --index) {

if (this.clientBranch.nodes[index].saved == false) {

this.clientBranch.nodes.pop();

} else {

break;

}

}

};

66

Page 67: Canva - magic mix drawings - Distributed Systems course 2014

Repository.prototype.deleteBranch = function(branchName) {

this.removeBranch(branchName);

var masterBranch = this.getBranchByName('master');

this.useBranch(masterBranch);

};

Repository.prototype.markSavedBranch = function(branchName) {

var branch = this.getBranchByName(branchName);

for (var index = branch.nodes.length - 1; index >= 0; --index) {

if (branch.nodes[index].saved == false) {

branch.nodes[index].saved = true;

} else {

break;

}

}

};

Repository.prototype.branchCheckedOut = function(serverBranch) {

if (!this.clientBranch) {

this.updateBranch(serverBranch);

this.useBranch(serverBranch);

return {

status: 'OK',

situation: 'FETCHED'

};

}

if (serverBranch.name != this.clientBranch.name) {

// We are checking out a different branch

this.clientBranch = this.getBranchByName(serverBranch.name);

return {

status: 'OK',

situation: 'SWITCHED'

};

}

// Find the last saved node

var index = this.clientBranch.nodes.length - 1;

while (index > 0 && this.clientBranch.nodes[index].saved == false) {

index--;

}

// We are checking out the same branch

if (serverBranch.nodes.length <= index + 1) {

// Server did no changes => return

return {

status: 'OK',

situation: 'KEPT'

};

}

// Server has changed something!

if (this.clientBranch.nodes.length - 1 > index) {

return {

status: 'ERROR',

situation: 'MERGE'

};

}

67

Page 68: Canva - magic mix drawings - Distributed Systems course 2014

// Client did not change anything => update to latest version.

this.updateBranch(serverBranch);

this.useBranch(serverBranch);

return {

status: 'OK',

situation: 'UPDATED'

};

};

Repository.prototype.mergeBranch = function(branchName) {

if (!this.clientBranch) {

return {

status: 'ERROR',

situation: 'CHECKOUT'

};

}

var childBranch = this.getBranchByName(branchName);

if (!childBranch) {

return {

status: 'ERROR',

situation: 'CHILD'

};

}

return {

status: 'OK',

situation: 'MERGE'

};

};

Repository.prototype.getConflictingNodes = function(serverBranch) {

if (!this.clientBranch) {

return {};

}

// Find the last saved node

var index = this.clientBranch.nodes.length - 1;

while (index > 0 && this.clientBranch.nodes[index].saved == false) {

index--;

}

return {

theirs: serverBranch.nodes[serverBranch.nodes.length - 1],

yours: this.clientBranch.nodes[this.clientBranch.nodes.length - 1],

last: this.clientBranch.nodes[index]

};

};

Repository.prototype.getToken = function(branchName) {

var blockade = this.findBlockade(branchName);

return blockade ? blockade.token : "";

}

Repository.prototype.blocked = function(data) {

this.removeBlockade(data.branch.name);

this.addBlockade({

name: data.branch.name,

token: data.token

});

};

68

Page 69: Canva - magic mix drawings - Distributed Systems course 2014

Repository.prototype.unblocked = function(data) {

this.removeBlockade(data.branch.name);

};

socket.js

function Socket(address, canvas, chat, repository, ui) {

var socket = io.connect(address);

var self = this;

// General socket events

socket.on('error', function (error) {

if (error.message) {

ui.notifyUser(error.message);

return;

}

console.log('Unable to connect to server', error);

});

socket.on('warning', function(warning) {

ui.notifyUser(warning.message);

});

// User related socket events

socket.on('connect', function () {

chat.setSessionId(socket.socket.sessionid);

socket.emit('newUser', {id: chat.getSessionId(), name: ui.getUserName()});

});

socket.on('newConnection', function (data) {

ui.updateParticipants(data.participants, chat.getSessionId());

});

socket.on('userDisconnected', function(data) {

ui.removeUser(data.id);

});

socket.on('nameChanged', function (data) {

ui.updateUser(data.id, data.name, chat.getSessionId());

});

socket.on('incomingMessage', function (data) {

ui.addMessage(data.message, data.name);

});

// Figure related socket events

socket.on('figureCreated', function (data) {

var clientBranch = repository.getBranch();

if (clientBranch && data.branch && clientBranch.name == data.branch.name) {

// Render the canvas

ui.showCanvas();

canvas.loadFromDatalessJSON(data.canvas);

canvas.renderAll();

// Try to save the branch with the token

69

Page 70: Canva - magic mix drawings - Distributed Systems course 2014

var token = repository.getToken(clientBranch.name);

if (token) {

self.commitBranch(canvas.toDatalessJSON(), true);

self.saveBranch(true);

}

}

});

// Branch related socket events

socket.on('updateBranchCreated', function (branches) {

ui.updateBranches(branches);

});

socket.on('updateBranchDeleted', function(name) {

ui.removeBranch(name);

});

socket.on('branchCheckedOut', function(serverBranch) {

var response = repository.branchCheckedOut(serverBranch);

if (response.status == 'OK') {

switch (response.situation) {

case 'FETCHED':

ui.notifyUser("The branch has been fetched.");

break;

case 'UPDATED':

ui.notifyUser("The branch has been updated.");

break;

case 'LEFT':

ui.notifyUser("You have checked out on branch "+ serverBranch.name

+ ". You can always check out the branch you are leaving.");

break;

case 'KEPT':

ui.notifyUser("You have checked out on branch "+ serverBranch.name

+ ". No changes found.");

break;

}

ui.showCanvas();

canvas.loadFromDatalessJSON(repository.getValue());

canvas.renderAll();

return;

}

if (response.status == 'ERROR') {

switch (response.situation) {

case 'MERGE':

var conflictingNodes =

repository.getConflictingNodes(serverBranch);

var conflictingValues = {

theirs: JSON.stringify(conflictingNodes.theirs.object),

yours: JSON.stringify(conflictingNodes.yours.object),

last: JSON.stringify(conflictingNodes.last.object)

};

ui.initiateMerge(true, conflictingValues, repository,

serverBranch);

}

}

});

70

Page 71: Canva - magic mix drawings - Distributed Systems course 2014

socket.on('branchCreated', function(branchName) {

repository.markSavedBranch(branchName);

ui.notifyUser("Branch " + branchName + " was created.");

});

socket.on('branchSaved', function(data) {

repository.markSavedBranch(data.name);

if (!data.automatic) {

ui.notifyUser("Branch " + data.name + " was saved.");

}

});

socket.on('branchDeleted', function(branchName) {

repository.deleteBranch(branchName);

ui.notifyUser("Branch " + branchName + " was deleted.");

});

socket.on('blocked', function(data) {

repository.blocked(data);

ui.notifyUser("Branch " + data.branch.name + " was blocked. Now all changes

happening in this branch will be saved on your behalf.");

});

socket.on('unblocked', function(data) {

repository.unblocked(data);

ui.notifyUser("Branch " + data.branch.name + " was unblocked.");

});

this.block = function() {

var branch = repository.getBranch();

var branchName = branch ? branch.name : "";

socket.emit("block", {

participant: {

id: chat.getSessionId()

},

branch: {

name: branchName

},

token: repository.getToken(branchName)

});

};

this.unblock = function() {

var branch = repository.getBranch();

var branchName = branch ? branch.name : "";

socket.emit("unblock", {

participant: {

id: chat.getSessionId()

},

branch: {

name: branchName

},

token: repository.getToken(branchName)

});

};

this.checkOutBranch = function(branchName) {

socket.emit("checkOutBranch", {id: chat.getSessionId(), name: branchName});

};

71

Page 72: Canva - magic mix drawings - Distributed Systems course 2014

this.createBranch = function(branchName) {

var response = repository.createBranch(branchName);

if (response.status == 'OK') {

socket.emit("saveBranch", {id: chat.getSessionId(), branch:

response.branch, automatic: false});

return;

}

if (response.status == 'ERROR') {

switch (response.situation) {

case 'CHECKOUT':

ui.notifyUser("Could not create branch. Checkout a branch first.");

break;

case 'RENAME':

ui.notifyUser("Could not create branch. A branch with name " +

branchName + " already exists. Create a branch with a different name.");

break;

case 'SAVE':

ui.notifyUser("Could not create branch because there are unsaved

changes. Save the branch first.");

break;

}

}

};

this.mergeBranch = function(branchName) {

var response = repository.mergeBranch(branchName);

if (response.status == 'ERROR') {

switch (response.situation) {

case 'CHECKOUT':

ui.notifyUser("Could not merge branch. Checkout a branch first.");

break;

case 'CHILD':

ui.notifyUser("Could not merge branch. Child branch does not

exist.");

break;

}

return;

}

if (response.status == 'OK') {

switch (response.situation) {

case 'MERGE':

var childBranch = repository.getBranchByName(branchName);

var conflictingNodes = repository.getConflictingNodes(childBranch);

var conflictingValues = {

theirs: JSON.stringify(conflictingNodes.theirs.object),

yours: JSON.stringify(conflictingNodes.yours.object),

last: JSON.stringify(conflictingNodes.last.object)

};

ui.initiateMerge(false, conflictingValues, repository, null);

}

}

};

this.commitBranch = function(objectToCommit, automatic) {

var response = repository.commitBranch(objectToCommit);

72

Page 73: Canva - magic mix drawings - Distributed Systems course 2014

if (response.status == 'OK') {

if (!automatic) {

ui.notifyUser("The change has been committed.");

}

return;

}

if (response.status == 'ERROR') {

switch (response.situation) {

case 'CHECKOUT':

ui.notifyUser("Could not commit branch. Checkout a branch first.");

break;

}

}

}

this.saveBranch = function(automatic) {

var clientBranch = repository.getBranch();

socket.emit("saveBranch", {id: chat.getSessionId(), branch: clientBranch,

token: repository.getToken(clientBranch ? clientBranch.name : ''), automatic:

automatic});

};

this.deleteBranch = function(branchName) {

socket.emit("deleteBranch", {id: chat.getSessionId(), name: branchName});

};

// Name related functions

this.nameFocusOut = function() {

socket.emit('nameChange', {id: chat.getSessionId(), name: ui.getUserName()});

};

// Figure related functions

this.figureCreated = function(canvas) {

socket.emit('newFigure', {

id: chat.getSessionId(),

canvas: canvas.toDatalessJSON(),

branch: repository.getBranch()

});

};

}

ui.js

function UI() {

var self = this;

this.notifyUser = function(message) {

alert(message);

};

this.updateParticipants = function(participants, userSessionId) {

$('#participants').html('');

for (var i = 0; i < participants.length; i++) {

$('#participants').append('<span id="user-' + participants[i].id + '">' +

participants[i].name + ' ' + (participants[i].id === userSessionId ?

'(You)' : '') + '<br /></span>');

73

Page 74: Canva - magic mix drawings - Distributed Systems course 2014

if (participants[i].id === userSessionId) {

$('#name').val(participants[i].name).attr('data-id', participants[i].id);

}

}

};

this.removeUser = function(userId) {

$('#' + userId).remove();

};

this.updateUser = function(userId, userName, userSessionId) {

$('#user-' + userId).html(userName + ' ' + (userId=== userSessionId? '(You)':

'') + '<br />');

};

this.addMessage = function(message, userName) {

$('#messages').append('<hr /><b>' + userName + '</b><br />' + message);

};

this.updateBranches = function(branches) {

$('#branches').html('');

for (var index in branches) {

$('#branches').append('<button id="branch-' + branches[index].name + '">'

+ branches[index].name + '</button>');

}

$('#branches button').on('click', function(event) {

$(event.target).siblings().removeClass('selected');

$(event.target).addClass('selected');

});

};

this.removeBranch = function(branchName) {

$('#branch-' + branchName).remove();

};

this.sendMessage = function() {

var outgoingMessage = $('#outgoingMessage').val();

var name = self.getUserName();

$.ajax({

url: '/message',

type: 'POST',

dataType: 'json',

data: {message: outgoingMessage, name: name}

});

};

this.outgoingMessageKeyDown = function(event) {

if (event.which == 13) {

event.preventDefault();

if ($('#outgoingMessage').val().trim().length <= 0) {

return;

}

self.sendMessage();

$('#outgoingMessage').val('');

}

};

74

Page 75: Canva - magic mix drawings - Distributed Systems course 2014

this.outgoingMessageKeyUp = function() {

var outgoingMessageValue = $('#outgoingMessage').val();

$('#send').attr('disabled', (outgoingMessageValue.trim()).length > 0 ? false :

true);

};

this.getUserName = function() {

return $('#name').val() ? $('#name').val() : "";

};

this.getUserId = function() {

return $('#name').attr('data-id') ? $('#name').attr('data-id') : "";

};

this.showCanvas = function() {

$('#workspace').show();

};

this.initiateMerge = function(isCheckout, conflictingValues, repository,

serverBranch) {

$('#merge').show();

var yours = new fabric.Canvas('yours');

var last = new fabric.Canvas('last');

var theirs = new fabric.Canvas('theirs');

var trash = new fabric.Canvas('trash');

yours.loadFromDatalessJSON(conflictingValues.yours);

yours.renderAll();

last.loadFromDatalessJSON(conflictingValues.last);

last.renderAll();

theirs.loadFromDatalessJSON(conflictingValues.theirs);

theirs.renderAll();

function objectExists(object) {

var lastObjects = last.getObjects();

for (var index in lastObjects) {

var lastObject = lastObjects[index];

if (JSON.stringify(object.toJSON()) ==

JSON.stringify(lastObject.toJSON())) {

return true;

}

}

return false;

};

var yoursObjects = yours.getObjects();

for (var index in yoursObjects) {

var object = yoursObjects[index];

object.lockMovementX = true;

object.lockMovementY = true;

object.lockRotation = true;

object.lockScalingX = true;

object.lockScalingY = true;

object.lockUniScaling = true;

if (objectExists(object)) {

object.selectable = false;

object.stroke = 'lightgray';

75

Page 76: Canva - magic mix drawings - Distributed Systems course 2014

object.fill = 'lightgray';

}

};

yours.renderAll();

var theirsObjects = theirs.getObjects();

for (var index in theirsObjects) {

var object = theirsObjects[index];

object.lockMovementX = true;

object.lockMovementY = true;

object.lockRotation = true;

object.lockScalingX = true;

object.lockScalingY = true;

object.lockUniScaling = true;

if (objectExists(object)) {

object.selectable = false;

object.stroke = 'lightgray';

object.fill = 'lightgray';

}

};

theirs.renderAll();

var lastObjects = last.getObjects();

for (var index in lastObjects) {

var object = lastObjects[index];

object.lockMovementX = true;

object.lockMovementY = true;

object.lockRotation = true;

object.lockScalingX = true;

object.lockScalingY = true;

object.lockUniScaling = true;

}

last.renderAll();

yours.on("object:selected", function(event) {

var object = event.target;

if (!object.selectable) {

return;

}

object.comingFrom = 'yours';

last.add(object);

last.renderAll();

yours.remove(object);

yours.renderAll();

});

theirs.on("object:selected", function(event) {

var object = event.target;

if (!object.selectable) {

return;

}

object.comingFrom = 'theirs';

last.add(object);

last.renderAll();

theirs.remove(object);

theirs.renderAll();

});

last.on("object:selected", function(event) {

var object = event.target;

76

Page 77: Canva - magic mix drawings - Distributed Systems course 2014

if (object.comingFrom == 'theirs') {

theirs.add(object);

theirs.renderAll();

} else if (object.comingFrom == 'yours') {

yours.add(object);

yours.renderAll();

} else {

trash.add(object);

trash.renderAll();

}

last.remove(object);

last.renderAll();

});

trash.on("object:selected", function(event) {

var object = event.target;

last.add(object);

last.renderAll();

trash.remove(object);

trash.renderAll();

});

$('#mergeok').off().on('click', function() {

var lastObjects = last.getObjects();

for (var index in lastObjects) {

var object = lastObjects[index];

delete object.comingFrom;

}

last.renderAll();

if (isCheckout) {

repository.updateBranch(serverBranch);

repository.useBranch(serverBranch);

}

repository.commitBranch(last.toDatalessJSON(), self.getUserName());

self.notifyUser("The branch merge has been resolved.");

$('#merge').hide();

});

$('#mergecancel').off().on('click', function() {

self.notifyUser("The branch merge has been aborted.");

$('#merge').hide();

});

};

}

style.css

body {

padding: 0;

font-family: 'Open Sans', sans-serif;

font-size: 1em;

}

index.jade

doctype html

77

Page 78: Canva - magic mix drawings - Distributed Systems course 2014

html

head

meta(charset='utf-8')

meta(name='author', content='Vlad Manea')

meta(name='description' content='Canva')

meta(name='robots' content='all,index,follow')

script(type='text/javascript', src="/socket.io/socket.io.js")

script(type='text/javascript',

src="/scripts/lib/jquery.min.js")

script(type='text/javascript',

src="/scripts/lib/bootstrap.colorpicker.js")

script(type='text/javascript',

src="/scripts/lib/underscore.min.js")

script(type='text/javascript',

src="/scripts/lib/fabric.min.js")

script(type='text/javascript',

src="/scripts/drawing_modules/circle.js")

script(type='text/javascript',

src="/scripts/drawing_modules/ellipse.js")

script(type='text/javascript',

src="/scripts/drawing_modules/rectangle.js")

script(type='text/javascript',

src="/scripts/drawing_modules/triangle.js")

script(type='text/javascript',

src="/scripts/drawing_modules/line.js")

script(type='text/javascript',

src="/scripts/drawing_modules/polyline.js")

script(type='text/javascript',

src="/scripts/drawing_modules/text.js")

script(type='text/javascript', src="/scripts/ui.js")

script(type='text/javascript', src="/scripts/repository.js")

script(type='text/javascript', src="/scripts/chat.js")

script(type='text/javascript', src="/scripts/socket.js")

script(type='text/javascript', src="/scripts/modifier.js")

78

Page 79: Canva - magic mix drawings - Distributed Systems course 2014

script(type='text/javascript', src="/scripts/painter.js")

script(type='text/javascript', src="/scripts/index.js")

link(rel='stylesheet',

href='http://fonts.googleapis.com/css?family=Open+Sans')

link(rel='stylesheet', href='/css/bootstrap.min.css')

link(rel='stylesheet', href='/css/bootstrap.colorpicker.css')

link(rel='stylesheet', href='/css/style.css')

title Canva

body

div#merge

table

tr

td Your

Changes

td Last

Checkout

td Their

Changes

tr

td

canvas#yours(width="400", height="200", style="border:1px solid #000000;")

td

canvas#last(width="400", height="200", style="border:1px solid #000000;")

td

canvas#theirs(width="400", height="200", style="border:1px solid #000000;")

tr

td

td

canvas#trash(width="400", height="200", style="border:1px solid #000000;")

79

Page 80: Canva - magic mix drawings - Distributed Systems course 2014

td

tr

td

td Recycle

Bin

td

button#mergeok ok

button#mergecancel cancel

hr

div#workspace

canvas#demo(width="400", height="200",

style="border:1px solid #000000;")

button#circle Circle

button#ellipse Ellipse

button#rectangle Rectangle

button#triangle Triangle

button#line Line

button#polyline Polyline

button#text Text

a#strokeColor.btn.small(href="#",

data-color-format="hex", data-color="rgb(255, 255, 255)")

Change stroke color

a#fillColor.btn.small(href="#",

data-color-format="hex", data-color="rgb(255, 255, 255)")

Change fill color

input#strokeWidth(type="range",

min="1", max="20")

hr

h1 Branches

div#currentValue

80

Page 81: Canva - magic mix drawings - Distributed Systems course 2014

div#branches

button#checkOutBranch checkout

button#createBranch create

button#commitBranch commit

button#saveBranch save

button#deleteBranch delete

button#mergeBranch merge

span |

button#block block

button#unblock unblock

hr

h1 Messages

div

div.inlineBlock

span Your name:

input(type="text", value="Anonymous Squirrel")#name

br

form#messageForm

textarea(rows="4", cols="50", placeholder="Share something!",

maxlength=200)#outgoingMessage

input(type="button", value="Share", disabled=true)#send

div.inlineBlock.topAligned

b Participants

br

div#participants

hr

div#messages

81

Page 82: Canva - magic mix drawings - Distributed Systems course 2014

B.3. Test code

server-repository-spec.js

var Repository = require("../repository");

describe("Repository", function() {

var repo;

beforeEach(function() {

repo = new Repository();

});

describe("block", function() {

it("should trigger error invalid if no data", function() {

var result = repo.block();

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data participant", function() {

var result = repo.block({branch: {name: 'name'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data participant id", function() {

var result = repo.block({participant: {}, branch: {name: 'name'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data branch", function() {

var result = repo.block({participant: {id: '1'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data branch name", function() {

var result = repo.block({participant: {id: '1'}, branch: {}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error missing if branch name does not exist", function() {

var result = repo.block({participant: {id: '1'}, branch: {name:

'missingName'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger error missing if branch is inactive", function() {

var branchName = "existingBranch";

82

Page 83: Canva - magic mix drawings - Distributed Systems course 2014

repo.saveBranch({name: branchName, active: false});

var result = repo.block({participant: {id: '1'}, branch: {name:

branchName}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger ok if no block existed", function() {

var branchName = "existingBranch";

var participantId = 1;

repo.saveBranch({branch: {name: branchName, nodes: [], active: true}});

var result = repo.block({participant: {id: participantId}, branch: {name:

branchName}});

expect(result.status).toEqual('OK');

expect(result.branch.name).toEqual(branchName);

expect(result.token).toBeDefined();

expect(repo.isBlocked(branchName)).toBeTruthy();

});

it("should trigger error if no token", function() {

var branchName = "existingBranch";

var participantId = 1;

var res = repo.saveBranch({branch: {name: branchName, nodes: [], active:

true}});

repo.block({participant: {id: participantId}, branch: {name:

branchName}});

var result = repo.block({participant: {id: participantId}, branch: {name:

branchName}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INUSE');

});

it("should trigger error if token is invalid", function() {

var branchName = "existingBranch";

var participantId = 1;

repo.saveBranch({branch: {name: branchName, nodes: [], active: true}});

var blockResponse = repo.block({participant: {id: participantId}, branch:

{name: branchName}});

var result = repo.block({participant: {id: participantId}, branch: {name:

branchName}, token: blockResponse.token + "garbage"});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INUSE');

});

it("should trigger ok if token is valid", function() {

var branchName = "existingBranch";

var participantId = 1;

repo.saveBranch({branch: {name: branchName, nodes: [], active: true}});

var blockResponse = repo.block({participant: {id: participantId}, branch:

{name: branchName}});

var result = repo.block({participant: {id: participantId}, branch: {name:

branchName}, token: blockResponse.token});

83

Page 84: Canva - magic mix drawings - Distributed Systems course 2014

expect(result.status).toEqual('OK');

expect(result.branch.name).toEqual(branchName);

expect(result.token).toBeDefined();

expect(repo.isBlocked(branchName)).toBeTruthy();

});

});

describe("unblock", function() {

it("should trigger error invalid if no data", function() {

var result = repo.unblock();

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data participant", function() {

var result = repo.unblock({branch: {name: 'name'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data participant id", function() {

var result = repo.unblock({participant: {}, branch: {name: 'name'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data branch", function() {

var result = repo.unblock({participant: {id: '1'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data branch name", function() {

var result = repo.unblock({participant: {id: '1'}, branch: {}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error if no token", function() {

var branchName = "existingBranch";

var participantId = 1;

var result = repo.unblock({participant: {id: participantId}, branch:

{name: branchName}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('FORBIDDEN');

});

it("should trigger error if token is invalid", function() {

var branchName = "existingBranch";

var participantId = 1;

repo.saveBranch({branch: {name: branchName, nodes: [], active: true}});

var blockResponse = repo.block({participant: {id: participantId}, branch:

{name: branchName}});

var result = repo.unblock({participant: {id: participantId}, branch:

84

Page 85: Canva - magic mix drawings - Distributed Systems course 2014

{name: branchName}, token: blockResponse.token + "garbage"});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('FORBIDDEN');

});

it("should trigger error missing if branch name does not exist", function() {

var branchName = "existingBranch";

var result = repo.unblock({participant: {id: 1}, branch: {name:

'otherName'}, token: 'any'});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger error missing if branch is inactive", function() {

var branchName = "existingBranch";

repo.saveBranch({name: branchName, active: false});

var result = repo.unblock({participant: {id: '1'}, branch: {name:

branchName}, token: 'any'});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger ok if token is valid", function() {

var branchName = "existingBranch";

var participantId = 1;

repo.saveBranch({branch: {name: branchName, nodes: [], active: true}});

var blockResponse = repo.block({participant: {id: participantId}, branch:

{name: branchName}});

var result = repo.unblock({participant: {id: participantId}, branch:

{name: branchName}, token: blockResponse.token});

expect(result.status).toEqual('OK');

expect(result.branch.name).toEqual(branchName);

expect(repo.isBlocked(branchName)).not.toBeTruthy();

});

});

describe("getActiveBranches", function() {

it("should only return the active branches", function() {

var branchName1 = "bn1";

var branchName2 = "bn2";

var branchName3 = "bn3";

repo.saveBranch({branch: {name: branchName1, nodes: [], active: true}});

repo.saveBranch({branch: {name: branchName2, nodes: [], active:

false}});

repo.saveBranch({branch: {name: branchName3, nodes: [], active:

false}});

var result = repo.getActiveBranches();

// There is an extra master branch created by default!

expect(result.length).toEqual(2);

});

});

85

Page 86: Canva - magic mix drawings - Distributed Systems course 2014

describe("checkOutBranch", function() {

it("should trigger error invalid if no data", function() {

var result = repo.checkOutBranch();

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data name", function() {

var result = repo.checkOutBranch({});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error missing if branch name does not exist", function() {

var result = repo.checkOutBranch({name: 'missingName'});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger error missing if branch is inactive", function() {

var branchName = "existingBranch";

repo.saveBranch({name: branchName, active: false});

var result = repo.checkOutBranch({name: branchName});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger ok", function() {

var branchName = "existingBranch";

var participantId = 1;

var branch = {name: branchName, nodes: [{id: 1}], active: true};

repo.saveBranch({branch: branch});

var result = repo.checkOutBranch({name: branchName});

expect(result.status).toEqual('OK');

expect(result.branch).toEqual(branch);

});

});

describe("deleteBranch", function() {

it("should trigger error invalid if no data", function() {

var result = repo.deleteBranch();

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data name", function() {

var result = repo.deleteBranch({});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error missing if branch name does not exist", function() {

var result = repo.deleteBranch({name: 'missingName'});

86

Page 87: Canva - magic mix drawings - Distributed Systems course 2014

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger error missing if branch is inactive", function() {

var branchName = "existingBranch";

repo.saveBranch({name: branchName, active: false});

var result = repo.deleteBranch({name: branchName});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger error if branch is master", function() {

var branchName = "existingBranch";

var participantId = 1;

var oldActiveBranches = repo.getActiveBranches().length;

var result = repo.deleteBranch({name: 'master'});

var newActiveBranches = repo.getActiveBranches().length;

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MASTER');

});

it("should trigger ok", function() {

var branchName = "existingBranch";

var participantId = 1;

repo.saveBranch({branch: {name: branchName, nodes: [], active: true}});

var oldActiveBranches = repo.getActiveBranches().length;

var result = repo.deleteBranch({name: branchName});

var newActiveBranches = repo.getActiveBranches().length;

expect(result.status).toEqual('OK');

expect(oldActiveBranches - newActiveBranches).toEqual(1);

});

});

describe("saveBranch", function() {

it("should trigger error invalid if no data", function() {

var result = repo.saveBranch();

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data branch", function() {

var result = repo.saveBranch({participant: {id: '1'}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

it("should trigger error invalid if no data branch name", function() {

var result = repo.saveBranch({participant: {id: '1'}, branch: {}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('INVALID');

});

87

Page 88: Canva - magic mix drawings - Distributed Systems course 2014

it("should trigger error missing if the branch exists but it is inactive",

function() {

var branchName = 'branchName';

repo.saveBranch({participant: {id: '1'}, branch: {name: branchName,

nodes: [], active: false}});

var result = repo.saveBranch({participant: {id: '1'}, branch: {name:

branchName, nodes: []}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger error forbidden if the branch is blocked and there is no

token", function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [], active: true}});

repo.block({participant: {id: participantId}, branch: {name:

branchName}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: []}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('FORBIDDEN');

});

it("should trigger error forbidden if the branch is blocked and the token is

invalid", function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [], active: true}});

var blockResponse = repo.block({participant: {id: participantId}, branch:

{name: branchName}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: []}, token: blockResponse.token + "garbage"});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('FORBIDDEN');

});

it("should trigger error client head if the client branch has no nodes",

function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: []}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('CLIENTHEAD');

});

it("should trigger error server head if the client branch has no nodes",

function() {

88

Page 89: Canva - magic mix drawings - Distributed Systems course 2014

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: ['some node representation']}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('SERVERHEAD');

});

it("should trigger error conflict if both client and server are ahead and client

has more", function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [{id: 1}, {id: 2}, {id: 3}], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: [{id: 1}, {id: 2}, {id: 5}, {id: 7}]}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('CONFLICT');

});

it("should trigger error conflict if both client and server are ahead and server

has more", function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [{id: 1}, {id: 2}, {id: 5}, {id: 7}], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: [{id: 1}, {id: 2}, {id: 3}]}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('CONFLICT');

});

it("should trigger error conflict if both client and server are ahead and server

has more", function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [{id: 1}, {id: 2}, {id: 5}, {id: 7}], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: [{id: 1}, {id: 2}, {id: 3}, {id: 4}]}});

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('CONFLICT');

});

it("should trigger ok saved if the client is ahead of the server", function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [{id: 1}, {id: 2}, {id: 3}], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: [{id: 1}, {id: 2}, {id: 3}, {id: 4}]}});

89

Page 90: Canva - magic mix drawings - Distributed Systems course 2014

expect(result.status).toEqual('OK');

expect(result.situation).toEqual('SAVED');

});

it("should trigger warning up to date if the server is ahead of the client",

function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name:

branchName, nodes: [{id: 1}, {id: 2}, {id: 3}, {id: 4}], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: [{id: 1}, {id: 2}, {id: 3}]}});

expect(result.status).toEqual('WARNING');

expect(result.situation).toEqual('UPTODATE');

});

it("should trigger warning up to date if none of the server andclientisahead",

function() {

var branchName = 'branchName';

var participantId = 1;

repo.saveBranch({participant: {id: participantId}, branch: {name: branchName,

nodes: [{id: 1}, {id: 2}, {id: 3}], active: true}});

var result = repo.saveBranch({participant: {id: participantId}, branch:

{name: branchName, nodes: [{id: 1}, {id: 2}, {id: 3}]}});

expect(result.status).toEqual('WARNING');

expect(result.situation).toEqual('UPTODATE');

});

it("should trigger ok if the branch did not exist", function() {

var oldActiveBranches = repo.getActiveBranches().length;

var result = repo.saveBranch({participant: {id: '1'}, branch: {name:

'newName', nodes: [], active: true}});

var newActiveBranches = repo.getActiveBranches().length;

expect(result.status).toEqual('OK');

expect(result.situation).toEqual('CREATED');

expect(newActiveBranches - oldActiveBranches).toEqual(1);

});

});

});

server-chat-spec.js

var Chat = require("../chat");

describe("Chat", function() {

var chat;

beforeEach(function() {

chat = new Chat();

});

describe("newUser", function() {

it("should trigger ok", function() {

var data = {id: 1, name: "John Doe"};

90

Page 91: Canva - magic mix drawings - Distributed Systems course 2014

var result = chat.newUser(data);

expect(result.status).toEqual('OK');

expect(result.participants.length).toEqual(1);

expect(result.participants[0]).toEqual(data);

});

});

describe("nameChange", function() {

it("should trigger error missing if no participant", function() {

var data = {id: 1, name: "John Doe"};

var result = chat.nameChange(data, 1);

expect(result.status).toEqual('ERROR');

expect(result.situation).toEqual('MISSING');

});

it("should trigger ok if participant exists", function() {

var data = {id: 1, name: "John Doe"};

chat.newUser(data);

var result = chat.nameChange(data, 1);

expect(result.status).toEqual('OK');

});

});

describe("disconnect", function() {

it("should trigger ok", function() {

var data = {id: 1, name: "John Doe"};

chat.newUser(data);

var result1 = chat.disconnect(data, 1);

var result2 = chat.disconnect(data, 2);

expect(result1.status).toEqual('OK');

expect(result2.status).toEqual('OK');

});

});

});

Appendix C

C.1. A passing build The steps consist of:

1. the environment is exported

2. the master branch of the Bitbucket repository is cloned

3. the system moves to the working directory

4. the latest version of the master branch is checked out

5. the dependency cache is prepared

6. the virtual machine is prepared

7. the test framework is loaded as an external dependency

8. the tests are run in the spec folder

9. the Heroku app key is fetched

91

Page 92: Canva - magic mix drawings - Distributed Systems course 2014

10. the access to Heroku is checked

11. the Heroku repository is added for push

12. the contents of the the Bitbucket branch are pushed to the Heroku repository

13. the Heroku application endpoint is checked

92

Page 93: Canva - magic mix drawings - Distributed Systems course 2014

93

Page 94: Canva - magic mix drawings - Distributed Systems course 2014

C.2. A failing build This build failed due to not being able to push to the Heroku repository. If one step

of the build fails, all subsequent steps fail, and the build itself fails.

94

Page 95: Canva - magic mix drawings - Distributed Systems course 2014

C.3. The latest build and deploy runs This is an overview of the build and deploy status of the project.

95

Page 96: Canva - magic mix drawings - Distributed Systems course 2014

96