diff --git a/pb/.gitignore b/pb/.gitignore new file mode 100644 index 000000000..1caad971e --- /dev/null +++ b/pb/.gitignore @@ -0,0 +1,5 @@ +target +*.swp +*~ +bin +.bundle diff --git a/pb/.ruby-gemset b/pb/.ruby-gemset new file mode 100644 index 000000000..f6191d354 --- /dev/null +++ b/pb/.ruby-gemset @@ -0,0 +1 @@ +jam-pb diff --git a/pb/.ruby-version b/pb/.ruby-version new file mode 100644 index 000000000..abf2ccea0 --- /dev/null +++ b/pb/.ruby-version @@ -0,0 +1 @@ +ruby-2.0.0-p247 diff --git a/pb/Gemfile b/pb/Gemfile new file mode 100644 index 000000000..7f7d5888b --- /dev/null +++ b/pb/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +workspace = ENV["WORKSPACE"] || "~/workspace" + +gem 'ruby-protocol-buffers', '1.2.2' diff --git a/pb/Gemfile.lock b/pb/Gemfile.lock new file mode 100644 index 000000000..5151acc4d --- /dev/null +++ b/pb/Gemfile.lock @@ -0,0 +1,10 @@ +GEM + remote: https://rubygems.org/ + specs: + ruby-protocol-buffers (1.2.2) + +PLATFORMS + ruby + +DEPENDENCIES + ruby-protocol-buffers (= 1.2.2) diff --git a/pb/README.md b/pb/README.md new file mode 100644 index 000000000..d4bfb340f --- /dev/null +++ b/pb/README.md @@ -0,0 +1,29 @@ +Jam-Pb (Protocol Buffers) +========================= + +Environment +----------- + +* Download and install protoc 2.4.1 on your PATH: [Protocol Buffers Download](http://code.google.com/p/protobuf/downloads/list) +* Bash (on windows, [Git Bash[(http://code.google.com/p/msysgit/) was used successfully)] +* Optional: if you have 'bundle' available (ruby dependency management tool), protocol buffers for ruby will also build. +* Optional: if you have protoc-c on your PATH, protocol buffers for c will also build. + +Building +-------- + +./build + +Using +----- +After a ./build: + +* ./target/cpp has cpp output +* ./target/ruby has ruby output +* ./target/c has c output +* ./target/java has java output + +Contributing +------------ + +Edit src/client-container.proto to add or change existing messages. That file has more contextual information in it. diff --git a/pb/build b/pb/build new file mode 100755 index 000000000..47f91d514 --- /dev/null +++ b/pb/build @@ -0,0 +1,25 @@ +#!/bin/bash + +TARGET=target +SRC=src + +# clean +rm -rf $TARGET +# prep output +mkdir $TARGET + +# find all protocol buffer files in src directory +#PROTO_FILES=`find src -name *.proto` +PROTO_FILES=src/client_container.proto + +# make variables available to sub-scripts +export SRC TARGET PROTO_FILES + +set -e + +# cpp/java/python supported by protoc +./build_protoc +# ruby supported by rprotoc +./build_rprotoc +# c supported by protoc-c +./build_protoc-c diff --git a/pb/build_protoc b/pb/build_protoc new file mode 100755 index 000000000..248406f32 --- /dev/null +++ b/pb/build_protoc @@ -0,0 +1,27 @@ +#!/bin/bash + +# this build file uses protoc to build all protocol buffers +# http://code.google.com/p/protobuf/downloads/list + + +TARGET_CPP="$TARGET/cpp" +TARGET_JAVA="$TARGET/java" + +mkdir -p "$TARGET_CPP" +mkdir -p "$TARGET_JAVA" + +# if you don't want to put protoc on the command line, +# then set a PROTOC environment variable +if [ -z $PROTOC] ; then + PROTOC="protoc" +fi + +command -v $PROTOC >/dev/null 2>&1 || { echo >&2 "protoc is required but not installed. Aborting"; exit 1; } + +# die on error at this point +set -e + +echo "building cpp protocol buffers" +$PROTOC $PROTO_FILES --cpp_out=$TARGET_CPP --proto_path=$SRC +echo "building java protocol buffers" +$PROTOC $PROTO_FILES --java_out=$TARGET_JAVA --proto_path=$SRC diff --git a/pb/build_protoc-c b/pb/build_protoc-c new file mode 100755 index 000000000..05171eaea --- /dev/null +++ b/pb/build_protoc-c @@ -0,0 +1,23 @@ +#!/bin/bash + +# this build file uses protoc to build all protocol buffers +# http://code.google.com/p/protobuf/downloads/list + +TARGET_C="$TARGET/c" + +mkdir -p "$TARGET_C" + +# if you don't want to put protoc on the command line, +# then set a PROTOC environment variable +if [ -z $PROTOCC] ; then + PROTOCC="protoc-c" +fi + +# running protoc-c is currently optional +command -v $PROTOCC >/dev/null 2>&1 || { echo >&2 "protoc-c is required but not installed. Skipping c protocol buffers."; exit 0; } + +# die on error at this point +set -e + +echo "building c protocol buffers" +$PROTOCC $PROTO_FILES --c_out=$TARGET_C --proto_path=$SRC diff --git a/pb/build_rprotoc b/pb/build_rprotoc new file mode 100755 index 000000000..e019cc519 --- /dev/null +++ b/pb/build_rprotoc @@ -0,0 +1,20 @@ +#!/bin/bash + +# ruby protocol buffers +# http://code.google.com/p/ruby-protobuf/ +RUBY_OUT=$TARGET/ruby + +# we exit with 0; treat ruby as optional at the moment +command -v "bundle" >/dev/null 2>&1 || { echo >&2 "bundle is required but not installed. Skipping ruby protocol buffers."; exit 0; } + +# creates a bin folder with 'rprotoc' command inside +bundle install --binstubs > /dev/null + +# die on error at this point +set -e + +echo "building ruby protocol buffers" +bin/ruby-protoc $PROTO_FILES --proto_path $SRC --ruby_out $RUBY_OUT + +./package_ruby + diff --git a/pb/ivy.xml b/pb/ivy.xml new file mode 100644 index 000000000..3e6df90b2 --- /dev/null +++ b/pb/ivy.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pb/ivysettings.xml b/pb/ivysettings.xml new file mode 100644 index 000000000..ba74af988 --- /dev/null +++ b/pb/ivysettings.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pb/jenkins b/pb/jenkins new file mode 100755 index 000000000..2d3bb662f --- /dev/null +++ b/pb/jenkins @@ -0,0 +1,25 @@ +#!/bin/bash + +GEM_SERVER=http://localhost:9000/gems + +echo "starting build..." +./build + +if [ "$?" = "0" ]; then + echo "build succeeded" + echo "publishing gem" + pushd "target/ruby/jampb" + find . -name *.gem -exec curl -f -T {} $GEM_SERVER/{} \; + + if [ "$?" != "0" ]; then + echo "publish failed" + exit 1 + fi + popd + echo "done publishing gems" +else + echo "build failed" + exit 1 +fi + + diff --git a/pb/package_java b/pb/package_java new file mode 100755 index 000000000..83bb0e8da --- /dev/null +++ b/pb/package_java @@ -0,0 +1,16 @@ +#!/bin/bash + +# not yet integrated into any formal build step. +# usage: after you've run ./build, run ./package_java to generate a jar +set -e + +echo "packaging java" +# retrieve protocol buffer dependency +java -jar $IVY -sync -retrieve "target/lib/[conf]/[artifact]-[type].[ext]" +# compile java file generated by protocol buffrs +javac -cp target/lib/default/protobuf-java-jar.jar target/java/jampb/ClientContainer.java +pushd target/java/jampb +# create jar with all classes generated by javac +jar cvf jampb-clientcontainer.jar `find . -name \*.class` +popd + diff --git a/pb/package_ruby b/pb/package_ruby new file mode 100755 index 000000000..65731589b --- /dev/null +++ b/pb/package_ruby @@ -0,0 +1,64 @@ +#!/bin/bash + +echo "packaging ruby protocol buffers" +pushd target/ruby > /dev/null + +rm -rf jampb + +bundle gem jampb > /dev/null + +# copy over built ruby code +cp src/*.rb jampb/lib/jampb + +if [ -z $BUILD_NUMBER ]; then + BUILD_NUMBER="1" +fi + +VERSION="0.0.${BUILD_NUMBER}" + +pushd jampb > /dev/null + +cat > lib/jampb/version.rb << EOF +module Jampb + VERSION = "$VERSION" +end +EOF + + +# define gemspec +cat >> jampb.gemspec << EOF + +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/jampb/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["Seth Call"] + gem.email = ["seth@jamkazam.com"] + gem.description = %q{protocol buffers for jamkazam} + gem.summary = %q{protocol buffers for jamkazam.} + gem.homepage = "http://www.jamkazam.com" + + gem.files = ['Gemfile', 'Rakefile', 'README.md', 'jampb.gemspec', 'lib/jampb.rb', 'lib/jampb/client_container.pb.rb', 'lib/jampb/version.rb' ] + gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.name = "jampb" + gem.require_paths = ["lib"] + gem.version = Jampb::VERSION +end +EOF + +# make sure library manifest includes the generated ruby file +cat >> lib/jampb.rb << EOF +require "jampb/version" +require "jampb/client_container.pb" + +module Jampb +end + +EOF + +gem build jampb.gemspec > /dev/null + +popd > /dev/null +popd > /dev/null + diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto new file mode 100644 index 000000000..31c0fcad4 --- /dev/null +++ b/pb/src/client_container.proto @@ -0,0 +1,322 @@ + +// adding a new message: +// within ClientMessage block, +// create a new field in the enum Type section (100s are meant to be normal messages; 1000s are meant to be errors). It will have new field number X +// create a optional message of the same name (but lower-case! the json code requires this), and use the same field number X. +// then at bottom of the file (or wherever; just outside any existing blocks), add your message definition. + +package jampb; + +message ClientMessage { + enum Type { + LOGIN = 100; + LOGIN_ACK = 101; + + LOGIN_MUSIC_SESSION = 102; + LOGIN_MUSIC_SESSION_ACK = 103; + FRIEND_SESSION_JOIN = 104; + LEAVE_MUSIC_SESSION = 105; + LEAVE_MUSIC_SESSION_ACK = 106; + HEARTBEAT = 107; + FRIEND_UPDATE = 108; + SESSION_INVITATION = 109; + MUSICIAN_SESSION_DEPART = 110; + JOIN_REQUEST = 111; + FRIEND_REQUEST = 112; + FRIEND_REQUEST_ACCEPTED = 113; + MUSICIAN_SESSION_JOIN = 114; + MUSICIAN_SESSION_FRESH = 115; + MUSICIAN_SESSION_STALE = 116; + HEARTBEAT_ACK = 117; + + TEST_SESSION_MESSAGE = 200; + + PING_REQUEST = 300; + PING_ACK = 301; + PEER_MESSAGE = 302; + TEST_CLIENT_MESSAGE = 303; + + SERVER_BAD_STATE_RECOVERED = 900; + + SERVER_GENERIC_ERROR = 1000; + SERVER_REJECTION_ERROR = 1001; + SERVER_PERMISSION_ERROR = 1002; + SERVER_BAD_STATE_ERROR = 1003; + } + + // Identifies which inner message is filled in + required Type type = 1; + required string route_to = 2; + optional string from = 3; + optional string message_id = 4; + optional string in_reply_to = 5; + + // One of the following messages can be populated: + + // Client-Server messages (to/from) + optional Login login = 100; // to server + optional LoginAck login_ack = 101; // from server + optional LoginMusicSession login_music_session = 102; // to server + optional LoginMusicSessionAck login_music_session_ack = 103; // from server + optional FriendSessionJoin friend_session_join = 104; // from server to all members + optional LeaveMusicSession leave_music_session = 105; + optional LeaveMusicSessionAck leave_music_session_ack = 106; + optional Heartbeat heartbeat = 107; + optional FriendUpdate friend_update = 108; // from server to all friends of user + optional SessionInvitation session_invitation = 109; // from server to user + optional MusicianSessionDepart musician_session_depart = 110; + optional JoinRequest join_request = 111; + optional FriendRequest friend_request = 112; + optional FriendRequestAccepted friend_request_accepted = 113; + optional MusicianSessionJoin musician_session_join = 114; + optional MusicianSessionFresh musician_session_fresh = 115; + optional MusicianSessionStale musician_session_stale = 116; + optional HeartbeatAck heartbeat_ack = 117; + + // Client-Session messages (to/from) + optional TestSessionMessage test_session_message = 200; + + // Client-Client messages (to/from) + optional PingRequest ping_request = 300; + optional PingAck ping_ack = 301; + optional PeerMessage peer_message = 302; + optional TestClientMessage test_client_message = 303; + + // Server-to-Client special messages + optional ServerBadStateRecovered server_bad_state_recovered = 900; + + // Server-to-Client errors + optional ServerGenericError server_generic_error = 1000; + optional ServerRejectionError server_rejection_error = 1001; + optional ServerPermissionError server_permission_error = 1002; + optional ServerBadStateError server_bad_state_error = 1003; +} + +// route_to: server +// sent from client to server to associate the connection with a user +// either supply a username/password, or just supply token +// if successful, a LoginAck is sent. +// if errored, a GenericServerError is sent and the connection is closed. +message Login { + optional string username = 1; // username... could be email. need to wait for prod requirements + optional string password = 2; // a password + optional string token = 3; // a token/cookie from previous successful login attempt or from 'token' field in .music file + optional string client_id = 4; // if supplied, the server will accept this client_id as the unique Id of this client instance + optional string reconnect_music_session_id = 5; // if supplied, the server will attempt to log the client into this session (designed for reconnect scenarios while in-session) + +} + +// route_to: client +// sent from server to client to let the client know the login was successful, +// and to also show the IP address of the client as seen by the server. +message LoginAck { + optional string public_ip = 1; + optional string client_id = 2; // a new client_id if none is supplied in Login, or just the original client_id echoed back + optional string token = 3; // the remember me token. This is useful if you logged in with username/password (otherwise it's just 'token' echoed back) + optional int32 heartbeat_interval = 4; // set your heartbeat interval to this value + optional string music_session_id = 5; // the music session that the user was in very recently (likely due to dropped connection) + optional bool reconnected = 6; // if reconnect_music_session_id is specified, and the server could log the user into that session, then true is returned. +} + +// route_to: server +// send from client to server to log in to a music session and be 'present'. +// if successful, a LoginMusicSessionAck is sent with error = false, and Client-Session messages will be passed +// directly from client to all other client. Also, the server will generate a 'UserJoinedMusicSession' message +// and send it to all other users to let the others know the user is here. +// if errored, a LoginMusicSessionAck is sent with error = true. +message LoginMusicSession { + optional string music_session = 1; +} + +// route_to: client +// error = true if the LoginMusicSession command failed. +message LoginMusicSessionAck { + optional bool error = 1; + optional string error_reason = 2; +} + +// route_to: server +// send from client to server to leave a previously joined music session +message LeaveMusicSession { + optional string music_session = 1; +} + +// route_to: client +// error = true if the session didn't exist, but otherwise this is intended to always work barring server unrecoverable errors +message LeaveMusicSessionAck { + optional bool error = 1; + optional string error_reason = 2; +} + +// route_to: client: +// sent by server to let the rest of the participants know a user has joined. +message FriendSessionJoin { + optional string session_id = 1; // the session ID + optional string user_id = 2; // this is the user_id and can be used for user unicast messages + optional string username = 3; // meant to be a display name + optional string photo_url = 4; +} + +// route_to: client: +// sent by server to let the rest of the participants know a user has joined. +message MusicianSessionJoin { + optional string session_id = 1; // the session ID + optional string user_id = 2; // this is the user_id and can be used for user unicast messages + optional string username = 3; // meant to be a display name + optional string photo_url = 4; +} + +// route_to: client: +// sent by server to let the rest of the participants know a user has left. +message MusicianSessionDepart { + optional string session_id = 1; // the session ID + optional string user_id = 2; // this is the user_id and can be used for user unicast messages + optional string username = 3; // meant to be a display name + optional string photo_url = 4; +} + +// route_to: client: +// sent by server to let the rest of the participants know a client has become active again after going stale +message MusicianSessionFresh { + optional string session_id = 1; // the session ID + optional string user_id = 2; // this is the user_id and can be used for user unicast messages + optional string username = 3; // meant to be a display name + optional string photo_url = 4; +} + +// route_to: client: +// sent by server to let the rest of the participants know a user has gone stale (websocket connection dropped) +message MusicianSessionStale { + optional string session_id = 1; // the session ID + optional string user_id = 2; // this is the user_id and can be used for user unicast messages + optional string username = 3; // meant to be a display name + optional string photo_url = 4; +} + +message JoinRequest { + optional string join_request_id = 1; + optional string username = 2; + optional string text = 3; +} + +// route_to: session +// a test message used by ruby-client currently. just gives way to send out to rest of session +message TestSessionMessage { + optional string msg = 1; +} + +// route_to: client:[CLIENT_ID] +// asks a client to begin a ping session +message PingRequest { + +} + +// route_to: client:[CLIENT_ID] +// tells a client to begin pinging a session +message PingAck { + +} + +// route_to: client:[CLIENT_ID] +// generic message between clients +message PeerMessage { + optional string message = 1; +} + +// route_to: client:[CLIENT_ID] +// a test message used by ruby-client currently. just gives a way to send directly to another client +message TestClientMessage { + optional string msg = 1; +} + +// route_to: server +// sent from client to server periodically to let server track if the client is truly alive and avoid TCP timeout scenarios +// the server will send a HeartbeatAck in response to this +message Heartbeat { + +} + +// target: client +// sent from server to client in response to a Heartbeat +message HeartbeatAck { +} + +// target: client +// send from server to client when user sends a friend request +message FriendRequest { + optional string friend_request_id = 1; + optional string user_id = 2; + optional string name = 3; + optional string photo_url = 4; + optional string friend_id = 5; + optional string msg = 6; + optional string notification_id = 7; + optional string created_at = 8; +} + +// target: client +message FriendRequestAccepted { + optional string friend_id = 1; // accepter + optional string name = 2; + optional string photo_url = 3; + optional string user_id = 4; // original requester + optional string msg = 5; + optional string notification_id = 6; + optional string created_at = 7; +} + +// target: client +// send from server to client when a user logs in +message FriendUpdate { + optional string user_id = 1; + optional string name = 2; + optional string photo_url = 3; + optional bool online = 4; + optional string msg = 5; +} + +// route_to: user:[USER_ID] +// let a user know they've been invited to a session +message SessionInvitation { + optional string invitation = 1; +} + +// route_to: client +// this should follow a ServerBadStateError in the case that the +// websocket gateway recovers from whatever ailed it +message ServerBadStateRecovered { +} + + + +// route_to: client +// this indicates unhandled error on server +// if you receive this, your connection will close after. +// but gives the client a chance to know why. +message ServerGenericError { + optional string error_msg = 1; +} + +// route_to: client +// this indicates the client did something wrong, and the server is mad enough to close connection. +// if you receive this, your connection will close after. +// but gives the client a chance to know why. +message ServerRejectionError { + optional string error_msg = 1; +} + +// route_to: client +// this indicates that the client doesn't have permission to ask the command +// your connection is not closed if this message occurs +message ServerPermissionError { + optional string error_msg = 1; +} + +// route_to: client +// this indicates that the server is in a bad state, but could recover later, and is unable to process the request +// your connection is not closed if this message occurs +message ServerBadStateError { + optional string error_msg = 1; +} + +