cockatoo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/Manifest +11 -0
- data/README.rdoc +51 -0
- data/Rakefile +12 -0
- data/bin/cockatoo +12 -0
- data/cockatoo.gemspec +47 -0
- data/extra/_chat.html.haml +182 -0
- data/extra/_chat_initialization.html.haml +38 -0
- data/extra/chat.js +376 -0
- data/lib/cockatoo_daemon.rb +26 -0
- data/lib/cockatoo_http_server.rb +205 -0
- metadata +169 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2010 Yann Klis
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/Manifest
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= Cockatoo
|
2
|
+
|
3
|
+
Cockatoo is a simple chat server. It's coded in Ruby with the help of the fantastic EventMachine.
|
4
|
+
|
5
|
+
There are several techniques to handle the real-time web, but we decided to use the Long Polling technique.
|
6
|
+
It works with plenty of browsers.
|
7
|
+
|
8
|
+
* homepage[http://github.com/novelys/cockatoo]
|
9
|
+
|
10
|
+
== Server part
|
11
|
+
|
12
|
+
The server part is coded in Ruby and EventMachine. The protocol is pretty straightforward.
|
13
|
+
|
14
|
+
== Client part
|
15
|
+
|
16
|
+
You can use any client part you want.
|
17
|
+
We provide 3 components so you can setup a simple chat system in any Ruby/Rails app you're working on :
|
18
|
+
* _chat_initialization[http://github.com/novelys/cockatoo/raw/master/extra/_chat_initialization.html.haml] HAML template
|
19
|
+
* _chat[http://github.com/novelys/cockatoo/raw/master/extra/_chat.html.haml] HAML template
|
20
|
+
* chat.js[http://github.com/novelys/cockatoo/raw/master/extra/chat.js] is the javascript client library (you will need jQuery[http://jquery.com])
|
21
|
+
|
22
|
+
Just include the 2 templates and the javascript lib in your layout and you'll be good to go.
|
23
|
+
|
24
|
+
Here are two screenshots of the client side in action :
|
25
|
+
* http://www.novelys.com/assets/Gallery/Lollidays/BigFrame5.png
|
26
|
+
* http://www.novelys.com/assets/Gallery/TheSphere/BigFrame3.png
|
27
|
+
|
28
|
+
== Installation
|
29
|
+
|
30
|
+
gem install cockatoo
|
31
|
+
|
32
|
+
== Usage
|
33
|
+
|
34
|
+
Beware that you'll need a "log" directory located where you will run cockatoo.
|
35
|
+
|
36
|
+
start the server (by default it will run on the 8000 port)
|
37
|
+
cockatoo start
|
38
|
+
|
39
|
+
start the server on the 9000 port
|
40
|
+
cockatoo start -- 9000
|
41
|
+
|
42
|
+
stop the server
|
43
|
+
cockatoo stop
|
44
|
+
|
45
|
+
restart the server
|
46
|
+
cockatoo restart
|
47
|
+
|
48
|
+
== Copyright
|
49
|
+
|
50
|
+
Copyright (c) 2010 Yann Klis. See LICENSE[http://github.com/novelys/cockatoo/raw/master/LICENSE] for details.
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'echoe'
|
2
|
+
Echoe.new('cockatoo', '0.0.1') do |p|
|
3
|
+
p.summary = "Cockatoo is a simple chat server"
|
4
|
+
p.description = "Cockatoo is a simple chat server coded with EventMachine and using the Long Polling technique"
|
5
|
+
p.url = "http://github.com/novelys/cockatoo"
|
6
|
+
p.author = "Yann Klis"
|
7
|
+
p.email = "yann.klis @nospam@ novelys.com"
|
8
|
+
p.ignore_pattern = ["tmp/*", "log/*"]
|
9
|
+
p.development_dependencies = []
|
10
|
+
p.runtime_dependencies = ["daemons >=1.1.0", "log4r >=1.1.8", "eventmachine >=0.12.10", "yajl-ruby >=0.7.0", "htmlentities >=4.2.1"]
|
11
|
+
p.executable_pattern = ["bin/*"]
|
12
|
+
end
|
data/bin/cockatoo
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'daemons'
|
3
|
+
|
4
|
+
file = File.join(File.expand_path(__FILE__), '..', '..', 'lib', 'cockatoo_daemon.rb')
|
5
|
+
pwd = Dir.pwd
|
6
|
+
Daemons.run(file,
|
7
|
+
:app_name => "cockatoo",
|
8
|
+
:backtrace => true,
|
9
|
+
:log_output => true,
|
10
|
+
:dir_mode => :normal,
|
11
|
+
:dir => File.join(pwd, "log")
|
12
|
+
)
|
data/cockatoo.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{cockatoo}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Yann Klis"]
|
9
|
+
s.date = %q{2010-10-27}
|
10
|
+
s.default_executable = %q{cockatoo}
|
11
|
+
s.description = %q{Cockatoo is a simple chat server coded with EventMachine and using the Long Polling technique}
|
12
|
+
s.email = %q{yann.klis @nospam@ novelys.com}
|
13
|
+
s.executables = ["cockatoo"]
|
14
|
+
s.extra_rdoc_files = ["LICENSE", "README.rdoc", "bin/cockatoo", "extra/_chat.html.haml", "extra/_chat_initialization.html.haml", "extra/chat.js", "lib/cockatoo_daemon.rb", "lib/cockatoo_http_server.rb"]
|
15
|
+
s.files = ["LICENSE", "Manifest", "README.rdoc", "Rakefile", "bin/cockatoo", "cockatoo.gemspec", "extra/_chat.html.haml", "extra/_chat_initialization.html.haml", "extra/chat.js", "lib/cockatoo_daemon.rb", "lib/cockatoo_http_server.rb"]
|
16
|
+
s.homepage = %q{http://github.com/novelys/cockatoo}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Cockatoo", "--main", "README.rdoc"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{cockatoo}
|
20
|
+
s.rubygems_version = %q{1.3.7}
|
21
|
+
s.summary = %q{Cockatoo is a simple chat server}
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<daemons>, [">= 1.1.0"])
|
29
|
+
s.add_runtime_dependency(%q<log4r>, [">= 1.1.8"])
|
30
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
|
31
|
+
s.add_runtime_dependency(%q<yajl-ruby>, [">= 0.7.0"])
|
32
|
+
s.add_runtime_dependency(%q<htmlentities>, [">= 4.2.1"])
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<daemons>, [">= 1.1.0"])
|
35
|
+
s.add_dependency(%q<log4r>, [">= 1.1.8"])
|
36
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
|
37
|
+
s.add_dependency(%q<yajl-ruby>, [">= 0.7.0"])
|
38
|
+
s.add_dependency(%q<htmlentities>, [">= 4.2.1"])
|
39
|
+
end
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<daemons>, [">= 1.1.0"])
|
42
|
+
s.add_dependency(%q<log4r>, [">= 1.1.8"])
|
43
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
|
44
|
+
s.add_dependency(%q<yajl-ruby>, [">= 0.7.0"])
|
45
|
+
s.add_dependency(%q<htmlentities>, [">= 4.2.1"])
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
#ChatApplication.ChatApplication.is-chat
|
2
|
+
.body.csb-body
|
3
|
+
.applets
|
4
|
+
.FriendsApplet.bar-container
|
5
|
+
.Bubble.decoration.TEMPLATE.hidden
|
6
|
+
.header.bbl-header
|
7
|
+
.title
|
8
|
+
%span.out-title
|
9
|
+
[TITLE]
|
10
|
+
%span.date.out-date
|
11
|
+
[DATE]
|
12
|
+
.do-close.action
|
13
|
+
x
|
14
|
+
.body.bbl-body
|
15
|
+
.message
|
16
|
+
.icon.left
|
17
|
+
%img.author.out-author.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "60", :width => "60" }
|
18
|
+
.text.out-text
|
19
|
+
[TEXT]
|
20
|
+
.clearer
|
21
|
+
\
|
22
|
+
.footer.bbl-footer
|
23
|
+
.operations
|
24
|
+
%button.do-ok.out-ok.action.T
|
25
|
+
reply
|
26
|
+
%span.hsep-5
|
27
|
+
%button.do-cancel.out-cancel.action.T
|
28
|
+
decline
|
29
|
+
.tip
|
30
|
+
\
|
31
|
+
.FriendsList.SlidingPane.hidden
|
32
|
+
.header.fl-header
|
33
|
+
.title.T
|
34
|
+
My friends
|
35
|
+
%img.do-collapse.action.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
36
|
+
.clearer
|
37
|
+
\
|
38
|
+
.body.fl-body
|
39
|
+
%ul.friends
|
40
|
+
%li.when-empty.when-service_available
|
41
|
+
.message.when-empty
|
42
|
+
%span.text.T
|
43
|
+
No online friend
|
44
|
+
%li.when-not_service_available.hidden
|
45
|
+
.message
|
46
|
+
%span.text.T
|
47
|
+
No service
|
48
|
+
/%button.action.do-reconnect.T
|
49
|
+
/ reconnect
|
50
|
+
.clearer
|
51
|
+
\
|
52
|
+
.clearer
|
53
|
+
\
|
54
|
+
.footer.TEMPLATES
|
55
|
+
%ul
|
56
|
+
%li.friend-element.when-service_available
|
57
|
+
.friend
|
58
|
+
.left
|
59
|
+
%img.icon.out-picture.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "30", :width => "30" }
|
60
|
+
.name.out-name
|
61
|
+
NAME
|
62
|
+
.status.out-status
|
63
|
+
STATUS
|
64
|
+
%span.when-not_in_discussion.hidden
|
65
|
+
%button.do-invite.action.T
|
66
|
+
invite
|
67
|
+
%span.when-in_discussion
|
68
|
+
%button.do-seeChat.action.T
|
69
|
+
chat
|
70
|
+
.clearer
|
71
|
+
\
|
72
|
+
.UserStatus.StatusBar
|
73
|
+
.body.us-body
|
74
|
+
.info
|
75
|
+
%img.P{ :src => "/images/icon-contact.png", :align => "absmiddle", :height => "16", :width => "16" }
|
76
|
+
%span
|
77
|
+
%span.hi
|
78
|
+
%span.out-onlineFriends
|
79
|
+
0
|
80
|
+
%span.T.out-onlineFriendsLabel
|
81
|
+
friends
|
82
|
+
%span
|
83
|
+
%span.T
|
84
|
+
online
|
85
|
+
.toggle
|
86
|
+
%img.do-expand.action.when-not_expanded.P{ :src => "/images/icon-expand.png", :align => "absmiddle", :height => "16", :width => "16" }
|
87
|
+
%img.do-collapse.action.when-expanded.hidden.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
88
|
+
.clearer
|
89
|
+
\
|
90
|
+
.footer.TEMPLATES
|
91
|
+
.DiscussionApplet.bar-container
|
92
|
+
.Bubble.decoration.TEMPLATE.hidden
|
93
|
+
.header.bbl-header
|
94
|
+
.title
|
95
|
+
%span.out-title
|
96
|
+
[TITLE]
|
97
|
+
%span.date.out-date
|
98
|
+
[DATE]
|
99
|
+
.do-close.action
|
100
|
+
x
|
101
|
+
.body.bbl-body
|
102
|
+
.message
|
103
|
+
.icon.left
|
104
|
+
%img.author.out-author.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "60", :width => "60" }
|
105
|
+
.text.out-text
|
106
|
+
[TEXT]
|
107
|
+
.clearer
|
108
|
+
\
|
109
|
+
.footer.bbl-footer
|
110
|
+
.operations
|
111
|
+
%button.do-ok.out-ok.action.T
|
112
|
+
reply
|
113
|
+
%span.hsep-5
|
114
|
+
%button.do-cancel.out-cancel.action.T
|
115
|
+
decline
|
116
|
+
.tip
|
117
|
+
\
|
118
|
+
.ChatLog.SlidingPane.decoration.hidden
|
119
|
+
.header.cl-header
|
120
|
+
.icon.left
|
121
|
+
%img.out-withPicture.P{ :src => "/images/user-avatar-default.png", :align => "absmiddle", :height => "45", :width => "45" }
|
122
|
+
.name.out-withName
|
123
|
+
NAME
|
124
|
+
.status.out-withStatus
|
125
|
+
STATUS
|
126
|
+
%img.do-collapse.action.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
127
|
+
%img.do-close-discussion.action.P{ :src => "/images/icon-close.png", :align => "absmiddle", :height => "16", :width => "16" }
|
128
|
+
.clearer
|
129
|
+
\
|
130
|
+
.header.cl-header-2
|
131
|
+
.left
|
132
|
+
.discussionStatus
|
133
|
+
%span.out
|
134
|
+
// TODO : find something interesting to display here...
|
135
|
+
|
136
|
+
.right
|
137
|
+
%span.on-state.when-initiated.when-accepted.when-active.when-inactive.hidden
|
138
|
+
%button.do-endDiscussion.action.T
|
139
|
+
end
|
140
|
+
%span.on-state.when-ended.when-cancelled.when-declined.hidden
|
141
|
+
%button.do-closeDiscussion.action.T
|
142
|
+
close
|
143
|
+
.clearer
|
144
|
+
\
|
145
|
+
.body.cl-body
|
146
|
+
%ul.messages.is-empty
|
147
|
+
%li.when-empty.hidden
|
148
|
+
.TEMPLATES
|
149
|
+
%ul
|
150
|
+
%li
|
151
|
+
.message
|
152
|
+
.header
|
153
|
+
.left.out.name
|
154
|
+
NAME
|
155
|
+
.right.out.date
|
156
|
+
HHhMM
|
157
|
+
.body.out.text
|
158
|
+
TEXT
|
159
|
+
.footer.cl-footer
|
160
|
+
.on-state.when-initiated.when-accepted.when-active.when-inactive
|
161
|
+
%input.in-message{ :type => "text" }
|
162
|
+
%button.do-writeMessage.action
|
163
|
+
%img.action.P{ :src => "/images/icon-bubble_and_shadow.png", :align => "absmiddle", :height => "16", :width => "16" }
|
164
|
+
.on-state.when-ended.when-cancelled.when-declined.hidden
|
165
|
+
%span.T
|
166
|
+
discussion ended
|
167
|
+
.ChatStatus.StatusBar
|
168
|
+
.body.us-body
|
169
|
+
.info
|
170
|
+
%img.P{ :src => "/images/icon-bubble.png", :align => "absmiddle", :height => "16", :width => "16" }
|
171
|
+
%span
|
172
|
+
%span.hi
|
173
|
+
%span.out-withName
|
174
|
+
NAME
|
175
|
+
\:
|
176
|
+
%span.out-messageCount
|
177
|
+
0
|
178
|
+
%span.T
|
179
|
+
messages
|
180
|
+
.toggle.action
|
181
|
+
%img.do-expand.when-not_expanded.hidden.action.P{ :src => "/images/icon-expand.png", :align => "absmiddle", :height => "16", :width => "16" }
|
182
|
+
%img.do-collapse.when-expanded.action.P{ :src => "/images/icon-collapse.png", :align => "absmiddle", :height => "16", :width => "16" }
|
@@ -0,0 +1,38 @@
|
|
1
|
+
:javascript
|
2
|
+
var ui_chat_applet = null;
|
3
|
+
var ui_chat_applet_status = null;
|
4
|
+
var ui_chat_applet_sliding_pane = null;
|
5
|
+
var ui_chat_applet_expanded = null;
|
6
|
+
var ui_chat_applet_collapsed = null;
|
7
|
+
var ui_chat_applet_friends = null;
|
8
|
+
var ui_chat_applet_friend_template = null;
|
9
|
+
|
10
|
+
var ui_chat_applets = null;
|
11
|
+
var ui_chat_friends_applet = null;
|
12
|
+
var ui_chat_discussion_applet_template = null;
|
13
|
+
|
14
|
+
var ui_chat_messages = null;
|
15
|
+
var ui_chat_message_template = null;
|
16
|
+
var friends_json = null;
|
17
|
+
var chat_initialization = null;
|
18
|
+
|
19
|
+
$(document).ready(function() {
|
20
|
+
ui_chat_applet = $('#ChatApplication')
|
21
|
+
ui_chat_applet_status = $('.StatusBar', ui_chat_applet)
|
22
|
+
ui_chat_applet_sliding_pane = $('.SlidingPane', ui_chat_applet)
|
23
|
+
ui_chat_applet_expanded = $('.when-expanded', ui_chat_applet)
|
24
|
+
ui_chat_applet_collapsed = $('.when-not_expanded', ui_chat_applet)
|
25
|
+
ui_chat_applet_friends = $('.fl-body .friends', ui_chat_applet)
|
26
|
+
ui_chat_applet_friend_template = $('.TEMPLATES li.friend-element', ui_chat_applet)
|
27
|
+
|
28
|
+
ui_chat_applets = $('.csb-body > .applets', ui_chat_applet)
|
29
|
+
ui_chat_friends_applet = $('.csb-body .FriendsApplet', ui_chat_applet)
|
30
|
+
ui_chat_discussion_applet_template = $('.TEMPLATES .DiscussionApplet', ui_chat_applet)
|
31
|
+
|
32
|
+
ui_chat_messages = $('ul.messages')
|
33
|
+
ui_chat_message_template = $('.TEMPLATES ul li')
|
34
|
+
|
35
|
+
load_friends_json();
|
36
|
+
|
37
|
+
chat_init();
|
38
|
+
});
|
data/extra/chat.js
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
function getKeys(h) {
|
2
|
+
var keys = "";
|
3
|
+
for (var key in h)
|
4
|
+
keys += "," + key;
|
5
|
+
return keys.substr(1);
|
6
|
+
}
|
7
|
+
|
8
|
+
function load_friends_json() {
|
9
|
+
$.ajax({
|
10
|
+
cache: false,
|
11
|
+
type: "GET",
|
12
|
+
dataType: "json",
|
13
|
+
url: "/profile/friends.json",
|
14
|
+
async: false,
|
15
|
+
//error: chat_unavailable,
|
16
|
+
success: function(msg) {
|
17
|
+
friends_json = msg;
|
18
|
+
}
|
19
|
+
});
|
20
|
+
}
|
21
|
+
|
22
|
+
function chat_init() {
|
23
|
+
chat_initialization = true;
|
24
|
+
chat_available();
|
25
|
+
chat_register();
|
26
|
+
|
27
|
+
$('.header', ui_chat_applet).click(function(){
|
28
|
+
chat_applet_toggle();
|
29
|
+
return false;
|
30
|
+
});
|
31
|
+
|
32
|
+
$('.info', ui_chat_applet_status).click(function(){
|
33
|
+
chat_applet_toggle();
|
34
|
+
return false;
|
35
|
+
});
|
36
|
+
$(ui_chat_applet_collapsed).click(function() {
|
37
|
+
chat_applet_toggle();
|
38
|
+
return false;
|
39
|
+
});
|
40
|
+
$(ui_chat_applet_expanded).click(function() {
|
41
|
+
chat_applet_toggle();
|
42
|
+
return false;
|
43
|
+
});
|
44
|
+
// ui_chat_applet.click(function(){
|
45
|
+
// chat_applet_focus()
|
46
|
+
// });
|
47
|
+
}
|
48
|
+
|
49
|
+
function chat_applet_toggle() {
|
50
|
+
chat_all_discussions_collapse();
|
51
|
+
|
52
|
+
if ( ui_chat_applet_status.hasClass('is-expanded') ) {
|
53
|
+
chat_applet_collapse();
|
54
|
+
} else {
|
55
|
+
chat_applet_expand();
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
function chat_applet_collapse() {
|
60
|
+
ui_chat_applet_sliding_pane.addClass('hidden');
|
61
|
+
ui_chat_applet_collapsed.removeClass('hidden');
|
62
|
+
ui_chat_applet_expanded.addClass('hidden');
|
63
|
+
ui_chat_applet_status.removeClass('is-expanded');
|
64
|
+
chat_applet_unfocus();
|
65
|
+
}
|
66
|
+
|
67
|
+
function is_all_chat_discussions_collapsed() {
|
68
|
+
return $('.ChatLog.hidden').length == $('.ChatLog').length;
|
69
|
+
}
|
70
|
+
|
71
|
+
function chat_applet_expand() {
|
72
|
+
ui_chat_applet_expanded.removeClass('hidden');
|
73
|
+
ui_chat_applet_collapsed.addClass('hidden');
|
74
|
+
ui_chat_applet_sliding_pane.removeClass('hidden');
|
75
|
+
ui_chat_applet_status.addClass('is-expanded');
|
76
|
+
chat_applet_focus();
|
77
|
+
}
|
78
|
+
|
79
|
+
function chat_applet_focus(){
|
80
|
+
$('#ChatApplication .with-focus').removeClass('with-focus');
|
81
|
+
ui_chat_applet.addClass('with-focus');
|
82
|
+
}
|
83
|
+
|
84
|
+
function chat_applet_unfocus(){
|
85
|
+
ui_chat_applet.removeClass('with-focus');
|
86
|
+
}
|
87
|
+
|
88
|
+
function chat_discussion_toggle(nui) {
|
89
|
+
if ($('.toggle .do-collapse', nui).hasClass('hidden')) {
|
90
|
+
chat_discussion_expand(nui);
|
91
|
+
} else {
|
92
|
+
chat_discussion_collapse(nui);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
function chat_all_discussions_collapse() {
|
97
|
+
$(".ChatLog").addClass('hidden');
|
98
|
+
$(".ChatStatus .toggle .do-collapse").addClass('hidden');
|
99
|
+
$(".ChatStatus .toggle .do-expand").removeClass('hidden');
|
100
|
+
}
|
101
|
+
|
102
|
+
function chat_discussion_expand(nui) {
|
103
|
+
chat_all_discussions_collapse();
|
104
|
+
$(".ChatLog", nui).removeClass('hidden');
|
105
|
+
$(".toggle .do-collapse", nui).removeClass('hidden');
|
106
|
+
$(".toggle .do-expand", nui).addClass('hidden');
|
107
|
+
chat_applet_collapse();
|
108
|
+
chat_scroll(nui);
|
109
|
+
focus_on_input(nui);
|
110
|
+
}
|
111
|
+
|
112
|
+
function focus_on_input(nui) {
|
113
|
+
$('.in-message', nui).focus();
|
114
|
+
}
|
115
|
+
|
116
|
+
function chat_discussion_collapse(nui) {
|
117
|
+
$(".ChatLog", nui).addClass('hidden');
|
118
|
+
$(".toggle .do-collapse", nui).addClass('hidden');
|
119
|
+
$(".toggle .do-expand", nui).removeClass('hidden');
|
120
|
+
chat_applet_collapse();
|
121
|
+
}
|
122
|
+
|
123
|
+
function chat_unavailable() {
|
124
|
+
$('.when-service_available', ui_chat_applet).addClass('hidden');
|
125
|
+
$('.DiscussionApplet', ui_chat_applets).addClass('hidden');
|
126
|
+
$('.when-not_service_available', ui_chat_applet).removeClass('hidden');
|
127
|
+
}
|
128
|
+
|
129
|
+
function chat_available() {
|
130
|
+
$('.when-service_available', ui_chat_applet).removeClass('hidden');
|
131
|
+
$('.DiscussionApplet', ui_chat_applets).removeClass('hidden');
|
132
|
+
$('.when-not_service_available', ui_chat_applet).addClass('hidden');
|
133
|
+
}
|
134
|
+
|
135
|
+
function chat_discussion_close(nui, fid) {
|
136
|
+
nui.remove();
|
137
|
+
|
138
|
+
$.ajax({
|
139
|
+
cache: false,
|
140
|
+
type: "GET",
|
141
|
+
dataType: "json",
|
142
|
+
url: "/chat/close_discussion",
|
143
|
+
error: chat_unavailable,
|
144
|
+
data: {uid: uid, did: fid}
|
145
|
+
});
|
146
|
+
}
|
147
|
+
|
148
|
+
function chat_register() {
|
149
|
+
url = "/chat/register";
|
150
|
+
$.ajax({
|
151
|
+
cache: false,
|
152
|
+
type: "GET",
|
153
|
+
dataType: "json",
|
154
|
+
url: url,
|
155
|
+
error: chat_unavailable,
|
156
|
+
success: chat_dispatch_messages,
|
157
|
+
data: {uid: uid, "friends_ids": getKeys(friends_json)}
|
158
|
+
});
|
159
|
+
}
|
160
|
+
|
161
|
+
function chat_poll() {
|
162
|
+
url = "/chat/poll";
|
163
|
+
$.ajax({
|
164
|
+
cache: false,
|
165
|
+
type: "GET",
|
166
|
+
dataType: "json",
|
167
|
+
url: url,
|
168
|
+
error: chat_unavailable,
|
169
|
+
success: chat_dispatch_messages,
|
170
|
+
data: {uid: uid}
|
171
|
+
});
|
172
|
+
}
|
173
|
+
|
174
|
+
function chat_say(fid, msg) {
|
175
|
+
url = "/chat/say";
|
176
|
+
$.ajax({
|
177
|
+
cache: false,
|
178
|
+
type: "GET",
|
179
|
+
dataType: "json",
|
180
|
+
url: url,
|
181
|
+
data: {sid: uid, did: fid, msg: msg}
|
182
|
+
});
|
183
|
+
}
|
184
|
+
|
185
|
+
function chat_leave() {
|
186
|
+
url = "/chat/leave";
|
187
|
+
$.ajax({
|
188
|
+
cache: false,
|
189
|
+
type: "GET",
|
190
|
+
dataType: "json",
|
191
|
+
url: url,
|
192
|
+
async: false,
|
193
|
+
data: {uid: uid}
|
194
|
+
});
|
195
|
+
}
|
196
|
+
|
197
|
+
function chat_dispatch_messages(messages) {
|
198
|
+
if (messages) {
|
199
|
+
for ( var i=0, len=messages.length; i<len; ++i ){
|
200
|
+
message = messages[i];
|
201
|
+
switch(message.type) {
|
202
|
+
case "status":
|
203
|
+
// user change his status : offline/online
|
204
|
+
chat_user_change_status(message);
|
205
|
+
break;
|
206
|
+
case "message":
|
207
|
+
// user send a message to another
|
208
|
+
chat_incoming_message(message);
|
209
|
+
break;
|
210
|
+
}
|
211
|
+
}
|
212
|
+
}
|
213
|
+
chat_initialization = false;
|
214
|
+
|
215
|
+
setTimeout(chat_poll, 0);
|
216
|
+
}
|
217
|
+
|
218
|
+
function chat_user_change_status(friend) {
|
219
|
+
not_existing = $('[fid='+friend.uid+']').length == 0;
|
220
|
+
if ( not_existing && (friend.state && (friend.state == 'ONLINE')) ) {
|
221
|
+
fui = ui_chat_applet_friend_template.clone();
|
222
|
+
fui.attr({'fid':('' + friend.uid), 'status':friend.state});
|
223
|
+
$('.out-name', fui).html(friends_json[friend.uid].login);
|
224
|
+
$('.out-status', fui).html(friend.state);
|
225
|
+
$('.out-picture', fui).attr({'title':friends_json[friend.uid].login, 'src':friends_json[friend.uid].avatar_url});
|
226
|
+
$('.do-seeChat', fui).click(function(){
|
227
|
+
chat_discuss_with_user(friend);
|
228
|
+
});
|
229
|
+
fui.removeClass('hidden');
|
230
|
+
ui_chat_applet_friends.append(fui);
|
231
|
+
chat_user_online(friend.uid);
|
232
|
+
}
|
233
|
+
if (friend.state && (friend.state == 'OFFLINE')) {
|
234
|
+
chat_user_offline(friend.uid);
|
235
|
+
}
|
236
|
+
chat_applet_update_counters();
|
237
|
+
}
|
238
|
+
|
239
|
+
function chat_user_offline(uid) {
|
240
|
+
$('[fid='+uid+']').remove();
|
241
|
+
nui = $('[discussion_with='+uid+']');
|
242
|
+
if (nui.length != 0) {
|
243
|
+
$('.out-withStatus', nui).html("OFFLINE");
|
244
|
+
$('.in-message', nui).attr("disabled", "disabled");
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
248
|
+
function chat_user_online(uid) {
|
249
|
+
nui = $('[discussion_with='+uid+']');
|
250
|
+
if (nui.length != 0) {
|
251
|
+
$('.out-withStatus', nui).html("ONLINE");
|
252
|
+
$('.in-message', nui).removeAttr("disabled");
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
function chat_scroll(nui) {
|
257
|
+
$('.cl-body', nui).scrollTo('100%');
|
258
|
+
}
|
259
|
+
|
260
|
+
function chat_incoming_message(message) {
|
261
|
+
var friend = null;
|
262
|
+
if (message.sid == uid) {
|
263
|
+
friend = {uid:message.did, state:"ONLINE"};
|
264
|
+
message_login = user_name;
|
265
|
+
} else {
|
266
|
+
friend = {uid:message.sid, state:"ONLINE"};
|
267
|
+
message_login = friends_json[friend.uid].login;
|
268
|
+
}
|
269
|
+
var nui = $('[discussion_with='+friend.uid+']');
|
270
|
+
if ( nui.length == 0 ) {
|
271
|
+
chat_discuss_with_user(friend);
|
272
|
+
nui = $('[discussion_with='+friend.uid+']');
|
273
|
+
}
|
274
|
+
|
275
|
+
var chat_log = $('.ChatLog', nui);
|
276
|
+
|
277
|
+
if (chat_initialization) {
|
278
|
+
chat_discussion_collapse(nui);
|
279
|
+
} else if (is_all_chat_discussions_collapsed()) {
|
280
|
+
chat_discussion_expand(nui);
|
281
|
+
}
|
282
|
+
|
283
|
+
var messages = $('ul.messages', chat_log);
|
284
|
+
chat_discussion_message_counter(nui, messages);
|
285
|
+
|
286
|
+
var message_template = $('.TEMPLATES ul li', chat_log).clone();
|
287
|
+
var date=new Date(message.date);
|
288
|
+
var m=date.getMinutes();
|
289
|
+
var h=date.getHours();
|
290
|
+
$('.out.name', message_template).html(message_login);
|
291
|
+
$('.out.text', message_template).html(message.value);
|
292
|
+
$('.out.date', message_template).html(sprintf('%02dh%02d', h, m));
|
293
|
+
if (message.sid == uid) {
|
294
|
+
message_template.addClass('inbound');
|
295
|
+
} else {
|
296
|
+
message_template.addClass('outbound');
|
297
|
+
}
|
298
|
+
messages.append(message_template).removeClass('is-empty');
|
299
|
+
|
300
|
+
chat_scroll(nui);
|
301
|
+
|
302
|
+
// Play a sound...
|
303
|
+
if (!chat_initialization && message.sid != uid) {
|
304
|
+
chatAlarmSound.play();
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
function chat_discuss_with_user(friend) {
|
309
|
+
var nui = $('[discussion_with='+friend.uid+']');
|
310
|
+
if ( nui.length == 0 ) {
|
311
|
+
var nui = ui_chat_discussion_applet_template.clone();
|
312
|
+
nui.attr({'discussion_with':friend.uid});
|
313
|
+
$('.out-withName', nui).html(friends_json[friend.uid].login);
|
314
|
+
$('.out-withStatus', nui).html(friend.state);
|
315
|
+
$('.out-withPicture', nui).attr({'title':friends_json[friend.uid].login, 'src':friends_json[friend.uid].avatar_url});
|
316
|
+
|
317
|
+
|
318
|
+
$('.ChatStatus.StatusBar', nui).click(function(){
|
319
|
+
chat_discussion_toggle(nui);
|
320
|
+
return false;
|
321
|
+
});
|
322
|
+
|
323
|
+
$('.ChatLog .header .do-collapse', nui).click(function() {
|
324
|
+
chat_discussion_toggle(nui);
|
325
|
+
return false;
|
326
|
+
});
|
327
|
+
|
328
|
+
$('.ChatLog .header .do-close-discussion', nui).click(function() {
|
329
|
+
chat_discussion_close(nui, friend.uid);
|
330
|
+
return false;
|
331
|
+
});
|
332
|
+
|
333
|
+
$(".in-message", nui).keypress(function (e) {
|
334
|
+
if (e.keyCode != 13 /* Return */) return;
|
335
|
+
chat_send_message(nui, friend.uid);
|
336
|
+
});
|
337
|
+
|
338
|
+
$(".do-writeMessage", nui).click(function() {
|
339
|
+
chat_send_message(nui, friend.uid);
|
340
|
+
});
|
341
|
+
|
342
|
+
ui_chat_applets.append(nui);
|
343
|
+
chat_applet_collapse();
|
344
|
+
focus_on_input(nui);
|
345
|
+
} else {
|
346
|
+
chat_discussion_expand(nui);
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
function chat_send_message(nui, friend_uid) {
|
351
|
+
/*var msg = $("#entry").attr("value").replace("\n", "");*/
|
352
|
+
var msg = $(".in-message", nui).attr("value");
|
353
|
+
if (msg != "") {
|
354
|
+
chat_say(friend_uid, msg);
|
355
|
+
$(".in-message", nui).attr("value", "");
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
function chat_discussion_message_counter(nui, messages) {
|
360
|
+
nbr = $('li', messages).length;
|
361
|
+
$('.out-messageCount', nui).html(nbr);
|
362
|
+
}
|
363
|
+
|
364
|
+
function chat_applet_update_counters() {
|
365
|
+
nbr = $('li.friend-element[STATUS=ONLINE]', ui_chat_applet_friends).length;
|
366
|
+
$('.out-onlineFriends', ui_chat_applet).html(nbr);
|
367
|
+
if (nbr > 0) {
|
368
|
+
$('.when-empty', ui_chat_applet_friends).addClass('hidden');
|
369
|
+
} else {
|
370
|
+
$('.when-empty', ui_chat_applet_friends).removeClass('hidden');
|
371
|
+
}
|
372
|
+
}
|
373
|
+
|
374
|
+
$(window).unload(function () {
|
375
|
+
chat_leave();
|
376
|
+
});
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__) + "/cockatoo_http_server"))
|
2
|
+
require 'log4r'
|
3
|
+
include Log4r
|
4
|
+
|
5
|
+
$logger = Logger.new 'cockatoo'
|
6
|
+
$logger.outputters = Outputter.stdout
|
7
|
+
|
8
|
+
|
9
|
+
loop do
|
10
|
+
begin
|
11
|
+
GC.start
|
12
|
+
$chat_users = {}
|
13
|
+
$timers = {}
|
14
|
+
EM.epoll if EM.epoll?
|
15
|
+
EM.run do
|
16
|
+
$logger.info "#{Time.now} Starting server on port #{ARGV.first || 8000}"
|
17
|
+
EM.start_server '0.0.0.0', ARGV.first || 8000, ChatHttpServer
|
18
|
+
end
|
19
|
+
rescue Interrupt
|
20
|
+
$logger.info "#{Time.now} Shuting down..."
|
21
|
+
exit
|
22
|
+
rescue
|
23
|
+
$logger.error "#{Time.now} Error: " + $!.message
|
24
|
+
$logger.error "\t" + $!.backtrace.join("\n\t")
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'cgi'
|
3
|
+
require 'yajl'
|
4
|
+
require "htmlentities"
|
5
|
+
|
6
|
+
class Object
|
7
|
+
def blank?
|
8
|
+
respond_to?(:empty?) ? empty? : !self
|
9
|
+
end
|
10
|
+
|
11
|
+
def present?
|
12
|
+
!blank?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ChatUser
|
17
|
+
attr_accessor :uid, :discussion, :friends_ids
|
18
|
+
def self.new_from_query(query)
|
19
|
+
return nil unless query.present? && query['uid'].present?
|
20
|
+
new(:uid => query['uid'].first.to_i)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(attributes)
|
24
|
+
@uid = attributes[:uid]
|
25
|
+
@friends_ids = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def log_with_user(other_chat_user)
|
29
|
+
self.discussion.present? ? self.discussion.log.select { |message| message["type"] == "message" && (message["sid"] == other_chat_user.uid || message["did"] == other_chat_user.uid) } : []
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear_discussion(did)
|
33
|
+
if self.discussion.present?
|
34
|
+
self.discussion.log.delete_if { |message| message["sid"] == did || message["did"] == did }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Discussion < EM::Channel
|
40
|
+
attr_accessor :log
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@log = []
|
44
|
+
super()
|
45
|
+
end
|
46
|
+
|
47
|
+
def say(ary)
|
48
|
+
ary = [ ary ].flatten
|
49
|
+
@log = (@log + ary)[0, 500]
|
50
|
+
json = Yajl::Encoder.encode(ary)
|
51
|
+
push json
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module ChatHttpServer
|
56
|
+
|
57
|
+
def unbind
|
58
|
+
$chat_users[@user_id].discussion.unsubscribe(@sid) if @user_id && @sid && $chat_users[@user_id] && $chat_users[@user_id].discussion
|
59
|
+
end
|
60
|
+
|
61
|
+
def receive_data(data)
|
62
|
+
# puts "received data : #{data}"
|
63
|
+
lines = data.split(/[\r\n]+/)
|
64
|
+
method, request, version = lines.shift.split(' ', 3)
|
65
|
+
if request.nil?
|
66
|
+
$logger.error "#{Time.now} Warning: strange request #{[method, request, version].inspect}"
|
67
|
+
close_connection
|
68
|
+
return
|
69
|
+
else
|
70
|
+
path, query = request.split('?', 2)
|
71
|
+
$logger.info "#{Time.now} request on #{path} with #{query}"
|
72
|
+
query = CGI.parse(query) if not query.nil?
|
73
|
+
cookies = {}
|
74
|
+
lines.each do |line|
|
75
|
+
if line[0..6].downcase == 'cookie:'
|
76
|
+
cookies = CGI::Cookie.parse(line.split(':', 2).last.strip)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if query.present? && query['uid'].present?
|
82
|
+
uid = query['uid'].first.to_i
|
83
|
+
chat_user = ($chat_users[uid] ||= ChatUser.new_from_query(query))
|
84
|
+
end
|
85
|
+
|
86
|
+
case path
|
87
|
+
|
88
|
+
when '/chat/register'
|
89
|
+
if chat_user && query['friends_ids'].present? && query['friends_ids'].first.present?
|
90
|
+
remove_all_timers_for(chat_user)
|
91
|
+
|
92
|
+
chat_user.friends_ids = query["friends_ids"].first.split(',').map { |id| id.to_i }
|
93
|
+
|
94
|
+
message = { "type" => "status", "state" => "ONLINE", "uid" => chat_user.uid }
|
95
|
+
back_messages = []
|
96
|
+
chat_user.friends_ids.each do |fid|
|
97
|
+
chat_user_friend = $chat_users[fid]
|
98
|
+
if chat_user_friend && chat_user_friend.discussion
|
99
|
+
chat_user_friend.discussion.say(message)
|
100
|
+
back_messages << {"type" => "status", "state" => "ONLINE", "uid" => fid}
|
101
|
+
|
102
|
+
back_messages += chat_user.discussion ? chat_user.log_with_user(chat_user_friend) : chat_user_friend.log_with_user(chat_user)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
respond Yajl::Encoder.encode(back_messages)
|
106
|
+
else
|
107
|
+
respond "no user id or no friends ids", 500
|
108
|
+
end
|
109
|
+
|
110
|
+
when '/chat/leave'
|
111
|
+
if chat_user
|
112
|
+
empty_response
|
113
|
+
unregister_user_after(chat_user, 15)
|
114
|
+
else
|
115
|
+
respond "no user id", 500
|
116
|
+
end
|
117
|
+
|
118
|
+
when '/chat/close_discussion'
|
119
|
+
empty_response
|
120
|
+
|
121
|
+
if chat_user && query['did'].present?
|
122
|
+
did = query['did'].first.to_i
|
123
|
+
$logger.info "User #{chat_user.uid} clears discussion for user #{did}"
|
124
|
+
chat_user.clear_discussion(did)
|
125
|
+
end
|
126
|
+
|
127
|
+
when '/chat/poll'
|
128
|
+
if chat_user
|
129
|
+
unregister_user_after(chat_user, 120)
|
130
|
+
timer = EventMachine::Timer.new(30) do
|
131
|
+
empty_response
|
132
|
+
end
|
133
|
+
@user_id = chat_user.uid
|
134
|
+
chat_user.discussion ||= Discussion.new
|
135
|
+
@sid = chat_user.discussion.subscribe do |code|
|
136
|
+
respond code
|
137
|
+
end
|
138
|
+
else
|
139
|
+
respond "no user id", 500
|
140
|
+
end
|
141
|
+
|
142
|
+
when '/chat/say'
|
143
|
+
if query['sid'].present? and query['msg'].present? and query['did'].present?
|
144
|
+
empty_response
|
145
|
+
sid = query['sid'].first.to_i
|
146
|
+
did = query['did'].first.to_i
|
147
|
+
msg = query['msg'].first
|
148
|
+
|
149
|
+
chat_user_sender = $chat_users[sid]
|
150
|
+
chat_user_recipient = $chat_users[did]
|
151
|
+
message = {"type" => "message", "value" => HTMLEntities.new.encode(msg.gsub('"', '\"')), "sid" => sid, "did" => did, "date" => (Time.now.to_i * 1000)}
|
152
|
+
|
153
|
+
if chat_user_sender && chat_user_recipient
|
154
|
+
chat_user_sender.discussion.say(message)
|
155
|
+
chat_user_recipient.discussion.say(message)
|
156
|
+
else
|
157
|
+
$logger.error "#{Time.now} ERROR : message could not be transmitted between sid #{sid} and did #{did}!"
|
158
|
+
end
|
159
|
+
else
|
160
|
+
respond "USER and MSG parameters are mandatory", 500
|
161
|
+
end
|
162
|
+
|
163
|
+
else
|
164
|
+
respond "not found", 404
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
RESPONSE = [
|
169
|
+
"HTTP/1.1 %d Cockatoo Alpha 1",
|
170
|
+
"Content-length: %d",
|
171
|
+
"Content-type: %s",
|
172
|
+
"Connection: close",
|
173
|
+
"",
|
174
|
+
"%s"].join("\r\n")
|
175
|
+
|
176
|
+
def empty_response
|
177
|
+
respond Yajl::Encoder.encode([])
|
178
|
+
end
|
179
|
+
|
180
|
+
def respond(body, status = 200, content_type = 'application/json; charset=utf-8')
|
181
|
+
send_data RESPONSE % [status, body.length, content_type, body]
|
182
|
+
close_connection_after_writing
|
183
|
+
end
|
184
|
+
|
185
|
+
def unregister_user_after(chat_user, seconds)
|
186
|
+
remove_all_timers_for(chat_user)
|
187
|
+
$logger.info "INFO : new timer for unregister user #{chat_user.uid} in #{seconds} sec."
|
188
|
+
$timers[chat_user.uid] = EventMachine::Timer.new(seconds) do
|
189
|
+
$logger.info "INFO : execute timer for user #{chat_user.uid}"
|
190
|
+
message = { "type" => "status", "state" => "OFFLINE", "uid" => chat_user.uid }
|
191
|
+
chat_user.friends_ids.each{|fid|
|
192
|
+
$chat_users[fid].discussion.say(message) if $chat_users[fid] && $chat_users[fid].discussion
|
193
|
+
}
|
194
|
+
$chat_users.delete(chat_user.uid)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def remove_all_timers_for(chat_user)
|
199
|
+
if $timers[chat_user.uid]
|
200
|
+
$logger.info "INFO : Removes timer for user #{chat_user.uid}"
|
201
|
+
$timers[chat_user.uid].cancel
|
202
|
+
$timers.delete(chat_user.uid)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cockatoo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Yann Klis
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-27 00:00:00 +02:00
|
19
|
+
default_executable: cockatoo
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: daemons
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 19
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 1
|
33
|
+
- 0
|
34
|
+
version: 1.1.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: log4r
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 1
|
49
|
+
- 8
|
50
|
+
version: 1.1.8
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: eventmachine
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 59
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 12
|
65
|
+
- 10
|
66
|
+
version: 0.12.10
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yajl-ruby
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
- 7
|
81
|
+
- 0
|
82
|
+
version: 0.7.0
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: htmlentities
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 53
|
94
|
+
segments:
|
95
|
+
- 4
|
96
|
+
- 2
|
97
|
+
- 1
|
98
|
+
version: 4.2.1
|
99
|
+
type: :runtime
|
100
|
+
version_requirements: *id005
|
101
|
+
description: Cockatoo is a simple chat server coded with EventMachine and using the Long Polling technique
|
102
|
+
email: yann.klis @nospam@ novelys.com
|
103
|
+
executables:
|
104
|
+
- cockatoo
|
105
|
+
extensions: []
|
106
|
+
|
107
|
+
extra_rdoc_files:
|
108
|
+
- LICENSE
|
109
|
+
- README.rdoc
|
110
|
+
- bin/cockatoo
|
111
|
+
- extra/_chat.html.haml
|
112
|
+
- extra/_chat_initialization.html.haml
|
113
|
+
- extra/chat.js
|
114
|
+
- lib/cockatoo_daemon.rb
|
115
|
+
- lib/cockatoo_http_server.rb
|
116
|
+
files:
|
117
|
+
- LICENSE
|
118
|
+
- Manifest
|
119
|
+
- README.rdoc
|
120
|
+
- Rakefile
|
121
|
+
- bin/cockatoo
|
122
|
+
- cockatoo.gemspec
|
123
|
+
- extra/_chat.html.haml
|
124
|
+
- extra/_chat_initialization.html.haml
|
125
|
+
- extra/chat.js
|
126
|
+
- lib/cockatoo_daemon.rb
|
127
|
+
- lib/cockatoo_http_server.rb
|
128
|
+
has_rdoc: true
|
129
|
+
homepage: http://github.com/novelys/cockatoo
|
130
|
+
licenses: []
|
131
|
+
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options:
|
134
|
+
- --line-numbers
|
135
|
+
- --inline-source
|
136
|
+
- --title
|
137
|
+
- Cockatoo
|
138
|
+
- --main
|
139
|
+
- README.rdoc
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
hash: 3
|
148
|
+
segments:
|
149
|
+
- 0
|
150
|
+
version: "0"
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
hash: 11
|
157
|
+
segments:
|
158
|
+
- 1
|
159
|
+
- 2
|
160
|
+
version: "1.2"
|
161
|
+
requirements: []
|
162
|
+
|
163
|
+
rubyforge_project: cockatoo
|
164
|
+
rubygems_version: 1.3.7
|
165
|
+
signing_key:
|
166
|
+
specification_version: 3
|
167
|
+
summary: Cockatoo is a simple chat server
|
168
|
+
test_files: []
|
169
|
+
|